diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c7c4ca5..0757780 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,7 +43,7 @@ jobs: build-ios: name: Build iOS - runs-on: [ macos-latest ] + runs-on: [ macos-14 ] steps: - uses: actions/checkout@v4 @@ -55,7 +55,7 @@ jobs: test-ios: name: Test iOS - runs-on: [ macos-latest ] + runs-on: [ macos-14 ] steps: - uses: actions/checkout@v4 diff --git a/splitio/LICENSE b/splitio/LICENSE index 68e2d99..af74bff 100644 --- a/splitio/LICENSE +++ b/splitio/LICENSE @@ -176,7 +176,7 @@ END OF TERMS AND CONDITIONS - Copyright © 2024 Split Software, Inc. + Copyright © 2025 Split Software, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/splitio/example/android/app/build.gradle b/splitio/example/android/app/build.gradle index e231f55..25dc71f 100644 --- a/splitio/example/android/app/build.gradle +++ b/splitio/example/android/app/build.gradle @@ -1,29 +1,8 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' +plugins { + id 'com.android.application' + id 'dev.flutter.flutter-gradle-plugin' } -apply plugin: 'com.android.application' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { compileSdk flutter.compileSdkVersion @@ -37,8 +16,8 @@ android { applicationId "io.split.splitio_example" minSdkVersion flutter.minSdkVersion targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName + versionCode 1 + versionName "1.0" } buildTypes { diff --git a/splitio/example/android/build.gradle b/splitio/example/android/build.gradle index 45f577c..bc157bd 100644 --- a/splitio/example/android/build.gradle +++ b/splitio/example/android/build.gradle @@ -1,14 +1,3 @@ -buildscript { - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:8.4.0' - } -} - allprojects { repositories { google() diff --git a/splitio/example/android/settings.gradle b/splitio/example/android/settings.gradle index 44e62bc..ea55ff6 100644 --- a/splitio/example/android/settings.gradle +++ b/splitio/example/android/settings.gradle @@ -1,11 +1,24 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" // apply true + id "com.android.application" version "8.4.0" apply false +} + +include ":app" \ No newline at end of file diff --git a/splitio/example/ios/Podfile.lock b/splitio/example/ios/Podfile.lock index ddaefcf..e004592 100644 --- a/splitio/example/ios/Podfile.lock +++ b/splitio/example/ios/Podfile.lock @@ -1,9 +1,9 @@ PODS: - Flutter (1.0.0) - - Split (3.0.0) - - splitio_ios (0.7.0): + - Split (3.3.2) + - splitio_ios (0.8.0): - Flutter - - Split (~> 3.0.0) + - Split (~> 3.3.2) DEPENDENCIES: - Flutter (from `Flutter`) @@ -21,9 +21,9 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - Split: 66424040ad573d052f58269f841e71b34578a916 - splitio_ios: e4e3becbe89cae0a2fa9ca03a575c21f23af0d90 + Split: 0d4962a6c15180e1857c1a3753e1ae9c91a6150b + splitio_ios: 438ad21d0dfe467670f8b9508773b77b16a71d6b PODFILE CHECKSUM: 4e8f8b2be68aeea4c0d5beb6ff1e79fface1d048 -COCOAPODS: 1.15.0 +COCOAPODS: 1.16.2 diff --git a/splitio/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/splitio/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e0c9630..4300fbd 100644 --- a/splitio/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/splitio/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> diff --git a/splitio/example/ios/Runner/AppDelegate.swift b/splitio/example/ios/Runner/AppDelegate.swift index 70693e4..b636303 100644 --- a/splitio/example/ios/Runner/AppDelegate.swift +++ b/splitio/example/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import UIKit import Flutter -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/splitio/example/pubspec.lock b/splitio/example/pubspec.lock index 8da00b5..624c7f2 100644 --- a/splitio/example/pubspec.lock +++ b/splitio/example/pubspec.lock @@ -5,42 +5,42 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.13.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" cupertino_icons: dependency: "direct main" description: @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" flutter: dependency: "direct main" description: flutter @@ -79,18 +79,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -111,34 +111,34 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.16.0" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" plugin_platform_interface: dependency: transitive description: @@ -151,86 +151,83 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" splitio: dependency: "direct main" description: path: ".." relative: true source: path - version: "0.2.0" + version: "1.0.0-rc.1" splitio_android: dependency: transitive description: - name: splitio_android - sha256: "44b0e1dddd374fc73fc1b5ef89598b96ea405d533a8211c06a45665f5d6187b5" - url: "https://pub.dev" - source: hosted - version: "0.2.0" + path: "../../splitio_android" + relative: true + source: path + version: "1.0.0-rc.1" splitio_ios: dependency: transitive description: - name: splitio_ios - sha256: "7c7a2a60711b8e6267cde7e2754d30931dafc76b20b28e1356624963628cb166" - url: "https://pub.dev" - source: hosted - version: "0.2.0" + path: "../../splitio_ios" + relative: true + source: path + version: "1.0.0-rc.1" splitio_platform_interface: dependency: transitive description: - name: splitio_platform_interface - sha256: "2f0457991d18d654486264a66dacf54c7cf23cd88bbb73ed299d69dbbc2fd49b" - url: "https://pub.dev" - source: hosted - version: "1.5.0" + path: "../../splitio_platform_interface" + relative: true + source: path + version: "2.0.0-rc.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.4" vector_math: dependency: transitive description: @@ -243,10 +240,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "15.0.0" sdks: - dart: ">=3.3.0 <4.0.0" + dart: ">=3.7.0-0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/splitio/lib/split_client.dart b/splitio/lib/split_client.dart index cb9b27f..883a9f4 100644 --- a/splitio/lib/split_client.dart +++ b/splitio/lib/split_client.dart @@ -18,7 +18,8 @@ abstract class SplitClient { /// /// Returns the evaluated treatment, the default treatment of this feature flag, or 'control'. Future getTreatment(String featureFlagName, - [Map attributes = const {}]); + [Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()]); /// Performs and evaluation and returns a [SplitResult] object for the /// [featureFlagName] feature flag. This object contains the treatment alongside the @@ -37,7 +38,8 @@ abstract class SplitClient { /// Optionally, a [Map] can be specified with the [attributes] parameter to /// take into account when evaluating. Future getTreatmentWithConfig(String featureFlagName, - [Map attributes = const {}]); + [Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()]); /// Convenience method to perform multiple evaluations. Returns a [Map] in /// which the keys are feature flag names and the values are treatments. @@ -47,7 +49,8 @@ abstract class SplitClient { /// Optionally, a [Map] can be specified with the [attributes] parameter to /// take into account when evaluating. Future> getTreatments(List featureFlagNames, - [Map attributes = const {}]); + [Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()]); /// Convenience method to perform multiple evaluations by flag set. Returns a [Map] in /// which the keys are feature flag names and the values are treatments. @@ -57,7 +60,8 @@ abstract class SplitClient { /// Optionally, a [Map] can be specified with the [attributes] parameter to /// take into account when evaluating. Future> getTreatmentsByFlagSet(String flagSet, - [Map attributes = const {}]); + [Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()]); /// Convenience method to perform multiple evaluations by flag sets. Returns a [Map] in /// which the keys are feature flag names and the values are treatments. @@ -67,7 +71,8 @@ abstract class SplitClient { /// Optionally, a [Map] can be specified with the [attributes] parameter to /// take into account when evaluating. Future> getTreatmentsByFlagSets(List flagSets, - [Map attributes = const {}]); + [Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()]); /// Convenience method to perform multiple evaluations by flag set. Returns a [Map] in /// which the keys are feature flag names and the values are [SplitResult] objects. @@ -76,8 +81,10 @@ abstract class SplitClient { /// /// Optionally, a [Map] can be specified with the [attributes] parameter to /// take into account when evaluating. - Future> getTreatmentsWithConfigByFlagSet(String flagSet, - [Map attributes = const {}]); + Future> getTreatmentsWithConfigByFlagSet( + String flagSet, + [Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()]); /// Convenience method to perform multiple evaluations by flag sets. Returns a [Map] in /// which the keys are feature flag names and the values are [SplitResult] objects. @@ -86,8 +93,10 @@ abstract class SplitClient { /// /// Optionally, a [Map] can be specified with the [attributes] parameter to /// take into account when evaluating. - Future> getTreatmentsWithConfigByFlagSets(List flagSets, - [Map attributes = const {}]); + Future> getTreatmentsWithConfigByFlagSets( + List flagSets, + [Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()]); /// Convenience method to perform multiple evaluations. Returns a [Map] in /// which the keys are feature flag names and the values are [SplitResult] objects. @@ -98,7 +107,8 @@ abstract class SplitClient { /// take into account when evaluating. Future> getTreatmentsWithConfig( List featureFlagNames, - [Map attributes = const {}]); + [Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()]); /// Enqueue a new event to be sent to Split data collection services. /// @@ -186,79 +196,105 @@ class DefaultSplitClient implements SplitClient { @override Future getTreatment(String featureFlagName, - [Map attributes = const {}]) async { + [Map attributes = const {}, + EvaluationOptions evaluationOptions = + const EvaluationOptions.empty()]) async { return _platform.getTreatment( matchingKey: _matchingKey, bucketingKey: _bucketingKey, splitName: featureFlagName, - attributes: attributes); + attributes: attributes, + evaluationOptions: evaluationOptions); } @override Future getTreatmentWithConfig(String featureFlagName, - [Map attributes = const {}]) async { + [Map attributes = const {}, + EvaluationOptions evaluationOptions = + const EvaluationOptions.empty()]) async { return _platform.getTreatmentWithConfig( matchingKey: _matchingKey, bucketingKey: _bucketingKey, splitName: featureFlagName, - attributes: attributes); + attributes: attributes, + evaluationOptions: evaluationOptions); } @override Future> getTreatments(List featureFlagNames, - [Map attributes = const {}]) async { + [Map attributes = const {}, + EvaluationOptions evaluationOptions = + const EvaluationOptions.empty()]) async { return _platform.getTreatments( matchingKey: _matchingKey, bucketingKey: _bucketingKey, splitNames: featureFlagNames, - attributes: attributes); + attributes: attributes, + evaluationOptions: evaluationOptions); } @override Future> getTreatmentsWithConfig( List featureFlagNames, - [Map attributes = const {}]) async { + [Map attributes = const {}, + EvaluationOptions evaluationOptions = + const EvaluationOptions.empty()]) async { return _platform.getTreatmentsWithConfig( matchingKey: _matchingKey, bucketingKey: _bucketingKey, splitNames: featureFlagNames, - attributes: attributes); + attributes: attributes, + evaluationOptions: evaluationOptions); } @override - Future> getTreatmentsByFlagSet(String flagSet, [Map attributes = const {}]) { + Future> getTreatmentsByFlagSet(String flagSet, + [Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()]) { return _platform.getTreatmentsByFlagSet( matchingKey: _matchingKey, bucketingKey: _bucketingKey, flagSet: flagSet, - attributes: attributes); + attributes: attributes, + evaluationOptions: evaluationOptions); } @override - Future> getTreatmentsByFlagSets(List flagSets, [Map attributes = const {}]) { + Future> getTreatmentsByFlagSets(List flagSets, + [Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()]) { return _platform.getTreatmentsByFlagSets( matchingKey: _matchingKey, bucketingKey: _bucketingKey, flagSets: flagSets, - attributes: attributes); + attributes: attributes, + evaluationOptions: evaluationOptions); } @override - Future> getTreatmentsWithConfigByFlagSet(String flagSet, [Map attributes = const {}]) { + Future> getTreatmentsWithConfigByFlagSet( + String flagSet, + [Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()]) { return _platform.getTreatmentsWithConfigByFlagSet( matchingKey: _matchingKey, bucketingKey: _bucketingKey, flagSet: flagSet, - attributes: attributes); + attributes: attributes, + evaluationOptions: evaluationOptions); } @override - Future> getTreatmentsWithConfigByFlagSets(List flagSets, [Map attributes = const {}]) { + Future> getTreatmentsWithConfigByFlagSets( + List flagSets, + [Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()]) { return _platform.getTreatmentsWithConfigByFlagSets( matchingKey: _matchingKey, bucketingKey: _bucketingKey, flagSets: flagSets, - attributes: attributes); + attributes: attributes, + evaluationOptions: evaluationOptions); } @override diff --git a/splitio/lib/splitio.dart b/splitio/lib/splitio.dart index 029d523..948c0b7 100644 --- a/splitio/lib/splitio.dart +++ b/splitio/lib/splitio.dart @@ -10,6 +10,7 @@ export 'package:splitio_platform_interface/split_result.dart'; export 'package:splitio_platform_interface/split_sync_config.dart'; export 'package:splitio_platform_interface/split_view.dart'; export 'package:splitio_platform_interface/split_certificate_pinning_configuration.dart'; +export 'package:splitio_platform_interface/split_evaluation_options.dart'; typedef ClientReadinessCallback = void Function(SplitClient splitClient); diff --git a/splitio/pubspec.yaml b/splitio/pubspec.yaml index 3e6d086..634f85e 100644 --- a/splitio/pubspec.yaml +++ b/splitio/pubspec.yaml @@ -1,6 +1,7 @@ +publish_to: none # TODO name: splitio description: Official plugin for split.io, the platform for controlled rollouts, which serves features to your users via feature flags to manage your complete customer experience. -version: 0.2.0 +version: 1.0.0-rc.1 homepage: https://split.io/ repository: https://github.com/splitio/flutter-sdk-plugin/tree/main/splitio/ @@ -19,10 +20,12 @@ flutter: dependencies: flutter: sdk: flutter - splitio_android: ^0.2.0 - splitio_ios: ^0.2.0 - splitio_platform_interface: ^1.5.0 - + splitio_android: # ^1.0.0-rc.1 + path: ../splitio_android + splitio_ios: # ^1.0.0-rc.1 + path: ../splitio_ios + splitio_platform_interface: # ^2.0.0-rc.1 + path: ../splitio_platform_interface dev_dependencies: flutter_test: sdk: flutter diff --git a/splitio/test/splitio_client_test.dart b/splitio/test/splitio_client_test.dart index de984a8..cac009a 100644 --- a/splitio/test/splitio_client_test.dart +++ b/splitio/test/splitio_client_test.dart @@ -21,6 +21,161 @@ void main() { _platform = SplitioPlatformStub(); }); + group('evaluationOptions tests', () { + test('getTreatment includes evaluationOptions when non-empty', () async { + final client = _getClient(); + final eo = EvaluationOptions({'x': 1, 'y': 'z'}); + + await client.getTreatment('split', {}, eo); + + expect(_platform.methodName, 'getTreatment'); + expect(_platform.methodArguments, { + 'splitName': 'split', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {}, + 'evaluationOptions': { + 'properties': {'x': 1, 'y': 'z'} + } + }); + }); + + test('getTreatmentWithConfig includes evaluationOptions when non-empty', + () async { + final client = _getClient(); + final eo = EvaluationOptions({'x': 1, 'y': 'z'}); + + await client.getTreatmentWithConfig('split1', {}, eo); + + expect(_platform.methodName, 'getTreatmentWithConfig'); + expect(_platform.methodArguments, { + 'splitName': 'split1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {}, + 'evaluationOptions': { + 'properties': {'x': 1, 'y': 'z'} + } + }); + }); + + test('getTreatments includes evaluationOptions when non-empty', () async { + final client = _getClient(); + final eo = EvaluationOptions({'x': 1, 'y': 'z'}); + + await client.getTreatments(['split1', 'split2'], {}, eo); + + expect(_platform.methodName, 'getTreatments'); + expect(_platform.methodArguments, { + 'splitName': ['split1', 'split2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {}, + 'evaluationOptions': { + 'properties': {'x': 1, 'y': 'z'} + } + }); + }); + + test('getTreatmentsWithConfig includes evaluationOptions when non-empty', + () async { + final client = _getClient(); + final eo = EvaluationOptions({'x': 1, 'y': 'z'}); + + await client.getTreatmentsWithConfig(['split1', 'split2'], {}, eo); + + expect(_platform.methodName, 'getTreatmentsWithConfig'); + expect(_platform.methodArguments, { + 'splitName': ['split1', 'split2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {}, + 'evaluationOptions': { + 'properties': {'x': 1, 'y': 'z'} + } + }); + }); + + test('getTreatmentsByFlagSet includes evaluationOptions when non-empty', + () async { + final client = _getClient(); + final eo = EvaluationOptions({'x': 1, 'y': 'z'}); + + await client.getTreatmentsByFlagSet('set_1', {}, eo); + + expect(_platform.methodName, 'getTreatmentsByFlagSet'); + expect(_platform.methodArguments, { + 'flagSet': 'set_1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {}, + 'evaluationOptions': { + 'properties': {'x': 1, 'y': 'z'} + } + }); + }); + + test('getTreatmentsByFlagSets includes evaluationOptions when non-empty', + () async { + final client = _getClient(); + final eo = EvaluationOptions({'x': 1, 'y': 'z'}); + + await client.getTreatmentsByFlagSets(['set_1', 'set_2'], {}, eo); + + expect(_platform.methodName, 'getTreatmentsByFlagSets'); + expect(_platform.methodArguments, { + 'flagSets': ['set_1', 'set_2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {}, + 'evaluationOptions': { + 'properties': {'x': 1, 'y': 'z'} + } + }); + }); + + test( + 'getTreatmentsWithConfigByFlagSet includes evaluationOptions when non-empty', + () async { + final client = _getClient(); + final eo = EvaluationOptions({'x': 1, 'y': 'z'}); + + await client.getTreatmentsWithConfigByFlagSet('set_1', {}, eo); + + expect(_platform.methodName, 'getTreatmentsWithConfigByFlagSet'); + expect(_platform.methodArguments, { + 'flagSet': 'set_1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {}, + 'evaluationOptions': { + 'properties': {'x': 1, 'y': 'z'} + } + }); + }); + + test( + 'getTreatmentsWithConfigByFlagSets includes evaluationOptions when non-empty', + () async { + final client = _getClient(); + final eo = EvaluationOptions({'x': 1, 'y': 'z'}); + + await client + .getTreatmentsWithConfigByFlagSets(['set_1', 'set_2'], {}, eo); + + expect(_platform.methodName, 'getTreatmentsWithConfigByFlagSets'); + expect(_platform.methodArguments, { + 'flagSets': ['set_1', 'set_2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {}, + 'evaluationOptions': { + 'properties': {'x': 1, 'y': 'z'} + } + }); + }); + }); + group('evaluation', () { test('getTreatment without attributes', () async { SplitClient client = _getClient(); @@ -235,7 +390,8 @@ void main() { test('getTreatmentsWithConfigByFlagSets with attributes', () async { SplitClient client = _getClient(); - client.getTreatmentsWithConfigByFlagSets(['set_1', 'set_2'], {'attr1': true}); + client.getTreatmentsWithConfigByFlagSets( + ['set_1', 'set_2'], {'attr1': true}); expect(_platform.methodName, 'getTreatmentsWithConfigByFlagSets'); expect(_platform.methodArguments, { diff --git a/splitio/test/splitio_platform_stub.dart b/splitio/test/splitio_platform_stub.dart index a996c97..001d7b3 100644 --- a/splitio/test/splitio_platform_stub.dart +++ b/splitio/test/splitio_platform_stub.dart @@ -1,5 +1,6 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:splitio_platform_interface/splitio_platform_interface.dart'; +import 'package:splitio_platform_interface/split_evaluation_options.dart'; class SplitioPlatformStub with MockPlatformInterfaceMixin @@ -89,7 +90,8 @@ class SplitioPlatformStub {required String matchingKey, required String? bucketingKey, required String splitName, - Map attributes = const {}}) { + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) { methodName = 'getTreatment'; methodArguments = { @@ -99,6 +101,10 @@ class SplitioPlatformStub 'attributes': attributes }; + if (evaluationOptions.properties.isNotEmpty) { + methodArguments['evaluationOptions'] = evaluationOptions.toJson(); + } + return Future.value(''); } @@ -107,7 +113,8 @@ class SplitioPlatformStub {required String matchingKey, required String? bucketingKey, required String splitName, - Map attributes = const {}}) { + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) { methodName = 'getTreatmentWithConfig'; methodArguments = { @@ -117,6 +124,10 @@ class SplitioPlatformStub 'attributes': attributes, }; + if (evaluationOptions.properties.isNotEmpty) { + methodArguments['evaluationOptions'] = evaluationOptions.toJson(); + } + return Future.value(const SplitResult('on', null)); } @@ -125,7 +136,8 @@ class SplitioPlatformStub {required String matchingKey, required String? bucketingKey, required List splitNames, - Map attributes = const {}}) { + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) { methodName = 'getTreatments'; methodArguments = { @@ -135,6 +147,10 @@ class SplitioPlatformStub 'attributes': attributes, }; + if (evaluationOptions.properties.isNotEmpty) { + methodArguments['evaluationOptions'] = evaluationOptions.toJson(); + } + return Future.value({}); } @@ -143,7 +159,8 @@ class SplitioPlatformStub {required String matchingKey, required String? bucketingKey, required List splitNames, - Map attributes = const {}}) { + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) { methodName = 'getTreatmentsWithConfig'; methodArguments = { @@ -153,6 +170,10 @@ class SplitioPlatformStub 'attributes': attributes, }; + if (evaluationOptions.properties.isNotEmpty) { + methodArguments['evaluationOptions'] = evaluationOptions.toJson(); + } + return Future.value({}); } @@ -161,7 +182,8 @@ class SplitioPlatformStub {required String matchingKey, required String? bucketingKey, required String flagSet, - Map attributes = const {}}) { + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) { methodName = 'getTreatmentsByFlagSet'; methodArguments = { @@ -171,6 +193,10 @@ class SplitioPlatformStub 'attributes': attributes, }; + if (evaluationOptions.properties.isNotEmpty) { + methodArguments['evaluationOptions'] = evaluationOptions.toJson(); + } + return Future.value({}); } @@ -179,7 +205,8 @@ class SplitioPlatformStub {required String matchingKey, required String? bucketingKey, required List flagSets, - Map attributes = const {}}) { + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) { methodName = 'getTreatmentsByFlagSets'; methodArguments = { @@ -189,6 +216,10 @@ class SplitioPlatformStub 'attributes': attributes, }; + if (evaluationOptions.properties.isNotEmpty) { + methodArguments['evaluationOptions'] = evaluationOptions.toJson(); + } + return Future.value({}); } @@ -197,7 +228,8 @@ class SplitioPlatformStub {required String matchingKey, required String? bucketingKey, required String flagSet, - Map attributes = const {}}) { + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) { methodName = 'getTreatmentsWithConfigByFlagSet'; methodArguments = { @@ -207,6 +239,10 @@ class SplitioPlatformStub 'attributes': attributes, }; + if (evaluationOptions.properties.isNotEmpty) { + methodArguments['evaluationOptions'] = evaluationOptions.toJson(); + } + return Future.value({}); } @@ -215,7 +251,8 @@ class SplitioPlatformStub {required String matchingKey, required String? bucketingKey, required List flagSets, - Map attributes = const {}}) { + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) { methodName = 'getTreatmentsWithConfigByFlagSets'; methodArguments = { @@ -225,6 +262,10 @@ class SplitioPlatformStub 'attributes': attributes, }; + if (evaluationOptions.properties.isNotEmpty) { + methodArguments['evaluationOptions'] = evaluationOptions.toJson(); + } + return Future.value({}); } diff --git a/splitio_android/LICENSE b/splitio_android/LICENSE index 68e2d99..af74bff 100644 --- a/splitio_android/LICENSE +++ b/splitio_android/LICENSE @@ -176,7 +176,7 @@ END OF TERMS AND CONDITIONS - Copyright © 2024 Split Software, Inc. + Copyright © 2025 Split Software, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/splitio_android/android/build.gradle b/splitio_android/android/build.gradle index f943883..0cd5200 100644 --- a/splitio_android/android/build.gradle +++ b/splitio_android/android/build.gradle @@ -1,6 +1,3 @@ -group 'io.split.splitio' -version '0.0.1' - buildscript { repositories { google() @@ -12,6 +9,10 @@ buildscript { } } +plugins { + id 'com.android.library' +} + rootProject.allprojects { repositories { google() @@ -19,7 +20,8 @@ rootProject.allprojects { } } -apply plugin: 'com.android.library' +group 'io.split.splitio' +version '0.0.1' android { compileSdk 31 @@ -36,7 +38,7 @@ android { } dependencies { - implementation 'io.split.client:android-client:5.0.0' + implementation 'io.split.client:android-client:5.3.1' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:3.12.4' diff --git a/splitio_android/android/src/main/java/io/split/splitio/Constants.java b/splitio_android/android/src/main/java/io/split/splitio/Constants.java index 4537843..eae5404 100644 --- a/splitio_android/android/src/main/java/io/split/splitio/Constants.java +++ b/splitio_android/android/src/main/java/io/split/splitio/Constants.java @@ -41,6 +41,7 @@ static class Argument { static final String SDK_CONFIGURATION = "sdkConfiguration"; static final String SPLIT_NAME = "splitName"; static final String ATTRIBUTES = "attributes"; + static final String EVALUATION_OPTIONS = "evaluationOptions"; static final String EVENT_TYPE = "eventType"; static final String TRAFFIC_TYPE = "trafficType"; static final String VALUE = "value"; diff --git a/splitio_android/android/src/main/java/io/split/splitio/EvaluationWrapper.java b/splitio_android/android/src/main/java/io/split/splitio/EvaluationWrapper.java index 62ed6d0..8f50160 100644 --- a/splitio_android/android/src/main/java/io/split/splitio/EvaluationWrapper.java +++ b/splitio_android/android/src/main/java/io/split/splitio/EvaluationWrapper.java @@ -3,22 +3,23 @@ import java.util.List; import java.util.Map; +import io.split.android.client.EvaluationOptions; import io.split.android.client.SplitResult; interface EvaluationWrapper { - String getTreatment(String matchingKey, String bucketingKey, String splitName, Map attributes); + String getTreatment(String matchingKey, String bucketingKey, String splitName, Map attributes, EvaluationOptions evaluationOptions); - Map getTreatments(String matchingKey, String bucketingKey, List splitNames, Map attributes); + Map getTreatments(String matchingKey, String bucketingKey, List splitNames, Map attributes, EvaluationOptions evaluationOptions); - SplitResult getTreatmentWithConfig(String matchingKey, String bucketingKey, String splitName, Map attributes); + SplitResult getTreatmentWithConfig(String matchingKey, String bucketingKey, String splitName, Map attributes, EvaluationOptions evaluationOptions); - Map getTreatmentsWithConfig(String matchingKey, String bucketingKey, List splitNames, Map attributes); + Map getTreatmentsWithConfig(String matchingKey, String bucketingKey, List splitNames, Map attributes, EvaluationOptions evaluationOptions); - Map getTreatmentsByFlagSet(String matchingKey, String bucketingKey, String flagSet, Map attributes); + Map getTreatmentsByFlagSet(String matchingKey, String bucketingKey, String flagSet, Map attributes, EvaluationOptions evaluationOptions); - Map getTreatmentsByFlagSets(String matchingKey, String bucketingKey, List flagSets, Map attributes); + Map getTreatmentsByFlagSets(String matchingKey, String bucketingKey, List flagSets, Map attributes, EvaluationOptions evaluationOptions); - Map getTreatmentsWithConfigByFlagSet(String matchingKey, String bucketingKey, String flagSet, Map attributes); + Map getTreatmentsWithConfigByFlagSet(String matchingKey, String bucketingKey, String flagSet, Map attributes, EvaluationOptions evaluationOptions); - Map getTreatmentsWithConfigByFlagSets(String matchingKey, String bucketingKey, List flagSets, Map attributes); + Map getTreatmentsWithConfigByFlagSets(String matchingKey, String bucketingKey, List flagSets, Map attributes, EvaluationOptions evaluationOptions); } diff --git a/splitio_android/android/src/main/java/io/split/splitio/ImpressionListenerImp.java b/splitio_android/android/src/main/java/io/split/splitio/ImpressionListenerImp.java index 838d76b..38d9c96 100644 --- a/splitio_android/android/src/main/java/io/split/splitio/ImpressionListenerImp.java +++ b/splitio_android/android/src/main/java/io/split/splitio/ImpressionListenerImp.java @@ -33,7 +33,6 @@ public void log(Impression impression) { public void close() { } - private static Map impressionToMap(final Impression impression) { final Map impressionMap = new HashMap<>(); @@ -45,6 +44,7 @@ private static Map impressionToMap(final Impression impression) impressionMap.put("appliedRule", impression.appliedRule()); impressionMap.put("changeNumber", impression.changeNumber()); impressionMap.put("attributes", impression.attributes()); + impressionMap.put("properties", impression.properties()); return impressionMap; } diff --git a/splitio_android/android/src/main/java/io/split/splitio/SplitClientConfigHelper.java b/splitio_android/android/src/main/java/io/split/splitio/SplitClientConfigHelper.java index ce83e03..2df8e0d 100644 --- a/splitio_android/android/src/main/java/io/split/splitio/SplitClientConfigHelper.java +++ b/splitio_android/android/src/main/java/io/split/splitio/SplitClientConfigHelper.java @@ -10,6 +10,7 @@ import java.util.Set; import java.util.concurrent.TimeUnit; +import io.split.android.client.RolloutCacheConfiguration; import io.split.android.client.ServiceEndpoints; import io.split.android.client.SplitClientConfig; import io.split.android.client.SplitFilter; @@ -51,6 +52,9 @@ class SplitClientConfigHelper { private static final String READY_TIMEOUT = "readyTimeout"; private static final String CERTIFICATE_PINNING_CONFIGURATION = "certificatePinningConfiguration"; private static final String CERTIFICATE_PINNING_CONFIGURATION_PINS = "pins"; + private static final String ROLLOUT_CACHE_CONFIGURATION = "rolloutCacheConfiguration"; + private static final String ROLLOUT_CACHE_CONFIGURATION_EXPIRATION = "expirationDays"; + private static final String ROLLOUT_CACHE_CONFIGURATION_CLEAR_ON_INIT = "clearOnInit"; /** * Creates a {@link SplitClientConfig} object from a map. @@ -242,6 +246,22 @@ static SplitClientConfig fromMap(@NonNull Map configurationMap, } } + Map rolloutCacheConfiguration = getObjectMap(configurationMap, ROLLOUT_CACHE_CONFIGURATION); + if (rolloutCacheConfiguration != null) { + Integer expirationDays = getInteger(rolloutCacheConfiguration, ROLLOUT_CACHE_CONFIGURATION_EXPIRATION); + Boolean clearOnInit = getBoolean(rolloutCacheConfiguration, ROLLOUT_CACHE_CONFIGURATION_CLEAR_ON_INIT); + if (expirationDays != null || clearOnInit != null) { + RolloutCacheConfiguration.Builder cacheConfigBuilder = RolloutCacheConfiguration.builder(); + if (expirationDays != null) { + cacheConfigBuilder.expirationDays(expirationDays); + } + if (clearOnInit != null) { + cacheConfigBuilder.clearOnInit(clearOnInit); + } + builder.rolloutCacheConfiguration(cacheConfigBuilder.build()); + } + } + return builder.serviceEndpoints(serviceEndpointsBuilder.build()).build(); } diff --git a/splitio_android/android/src/main/java/io/split/splitio/SplitMethodParserImpl.java b/splitio_android/android/src/main/java/io/split/splitio/SplitMethodParserImpl.java index 812143c..1f4d248 100644 --- a/splitio_android/android/src/main/java/io/split/splitio/SplitMethodParserImpl.java +++ b/splitio_android/android/src/main/java/io/split/splitio/SplitMethodParserImpl.java @@ -4,6 +4,7 @@ import static io.split.splitio.Constants.Argument.ATTRIBUTES; import static io.split.splitio.Constants.Argument.ATTRIBUTE_NAME; import static io.split.splitio.Constants.Argument.BUCKETING_KEY; +import static io.split.splitio.Constants.Argument.EVALUATION_OPTIONS; import static io.split.splitio.Constants.Argument.EVENT_TYPE; import static io.split.splitio.Constants.Argument.FLAG_SET; import static io.split.splitio.Constants.Argument.FLAG_SETS; @@ -57,9 +58,11 @@ import java.util.Map; import io.flutter.plugin.common.MethodChannel; +import io.split.android.client.EvaluationOptions; import io.split.android.client.SplitClient; import io.split.android.client.SplitResult; import io.split.android.client.api.SplitView; +import io.split.android.client.dtos.Prerequisite; import io.split.android.client.events.SplitEvent; import io.split.android.client.events.SplitEventTask; @@ -115,56 +118,64 @@ public void onMethodCall(String methodName, Object arguments, @NonNull MethodCha mArgumentParser.getStringArgument(MATCHING_KEY, arguments), mArgumentParser.getStringArgument(BUCKETING_KEY, arguments), mArgumentParser.getStringArgument(SPLIT_NAME, arguments), - mArgumentParser.getMapArgument(ATTRIBUTES, arguments))); + mArgumentParser.getMapArgument(ATTRIBUTES, arguments), + buildEvaluationOptions(mArgumentParser.getMapArgument(EVALUATION_OPTIONS, arguments)))); break; case GET_TREATMENTS: result.success(getTreatments( mArgumentParser.getStringArgument(MATCHING_KEY, arguments), mArgumentParser.getStringArgument(BUCKETING_KEY, arguments), mArgumentParser.getStringListArgument(SPLIT_NAME, arguments), - mArgumentParser.getMapArgument(ATTRIBUTES, arguments))); + mArgumentParser.getMapArgument(ATTRIBUTES, arguments), + buildEvaluationOptions(mArgumentParser.getMapArgument(EVALUATION_OPTIONS, arguments)))); break; case GET_TREATMENT_WITH_CONFIG: result.success(getTreatmentWithConfig( mArgumentParser.getStringArgument(MATCHING_KEY, arguments), mArgumentParser.getStringArgument(BUCKETING_KEY, arguments), mArgumentParser.getStringArgument(SPLIT_NAME, arguments), - mArgumentParser.getMapArgument(ATTRIBUTES, arguments))); + mArgumentParser.getMapArgument(ATTRIBUTES, arguments), + buildEvaluationOptions(mArgumentParser.getMapArgument(EVALUATION_OPTIONS, arguments)))); break; case GET_TREATMENTS_WITH_CONFIG: result.success(getTreatmentsWithConfig( mArgumentParser.getStringArgument(MATCHING_KEY, arguments), mArgumentParser.getStringArgument(BUCKETING_KEY, arguments), mArgumentParser.getStringListArgument(SPLIT_NAME, arguments), - mArgumentParser.getMapArgument(ATTRIBUTES, arguments))); + mArgumentParser.getMapArgument(ATTRIBUTES, arguments), + buildEvaluationOptions(mArgumentParser.getMapArgument(EVALUATION_OPTIONS, arguments)))); break; case GET_TREATMENTS_BY_FLAG_SET: result.success(getTreatmentsByFlagSet( mArgumentParser.getStringArgument(MATCHING_KEY, arguments), mArgumentParser.getStringArgument(BUCKETING_KEY, arguments), mArgumentParser.getStringArgument(FLAG_SET, arguments), - mArgumentParser.getMapArgument(ATTRIBUTES, arguments))); + mArgumentParser.getMapArgument(ATTRIBUTES, arguments), + buildEvaluationOptions(mArgumentParser.getMapArgument(EVALUATION_OPTIONS, arguments)))); break; case GET_TREATMENTS_BY_FLAG_SETS: result.success(getTreatmentsByFlagSets( mArgumentParser.getStringArgument(MATCHING_KEY, arguments), mArgumentParser.getStringArgument(BUCKETING_KEY, arguments), mArgumentParser.getStringListArgument(FLAG_SETS, arguments), - mArgumentParser.getMapArgument(ATTRIBUTES, arguments))); + mArgumentParser.getMapArgument(ATTRIBUTES, arguments), + buildEvaluationOptions(mArgumentParser.getMapArgument(EVALUATION_OPTIONS, arguments)))); break; case GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET: result.success(getTreatmentsWithConfigByFlagSet( mArgumentParser.getStringArgument(MATCHING_KEY, arguments), mArgumentParser.getStringArgument(BUCKETING_KEY, arguments), mArgumentParser.getStringArgument(FLAG_SET, arguments), - mArgumentParser.getMapArgument(ATTRIBUTES, arguments))); + mArgumentParser.getMapArgument(ATTRIBUTES, arguments), + buildEvaluationOptions(mArgumentParser.getMapArgument(EVALUATION_OPTIONS, arguments)))); break; case GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS: result.success(getTreatmentsWithConfigByFlagSets( mArgumentParser.getStringArgument(MATCHING_KEY, arguments), mArgumentParser.getStringArgument(BUCKETING_KEY, arguments), mArgumentParser.getStringListArgument(FLAG_SETS, arguments), - mArgumentParser.getMapArgument(ATTRIBUTES, arguments))); + mArgumentParser.getMapArgument(ATTRIBUTES, arguments), + buildEvaluationOptions(mArgumentParser.getMapArgument(EVALUATION_OPTIONS, arguments)))); break; case TRACK: result.success(track( @@ -277,23 +288,26 @@ private SplitClient getClient(String matchingKey, String bucketingKey) { private String getTreatment(String matchingKey, String bucketingKey, String splitName, - Map attributes) { - return mSplitWrapper.getTreatment(matchingKey, bucketingKey, splitName, attributes); + Map attributes, + EvaluationOptions evaluationOptions) { + return mSplitWrapper.getTreatment(matchingKey, bucketingKey, splitName, attributes, evaluationOptions); } private Map getTreatments(String matchingKey, String bucketingKey, List splitNames, - Map attributes) { - return mSplitWrapper.getTreatments(matchingKey, bucketingKey, splitNames, attributes); + Map attributes, + EvaluationOptions evaluationOptions) { + return mSplitWrapper.getTreatments(matchingKey, bucketingKey, splitNames, attributes, evaluationOptions); } private Map> getTreatmentWithConfig( String matchingKey, String bucketingKey, String splitName, - Map attributes) { - SplitResult treatment = mSplitWrapper.getTreatmentWithConfig(matchingKey, bucketingKey, splitName, attributes); + Map attributes, + EvaluationOptions evaluationOptions) { + SplitResult treatment = mSplitWrapper.getTreatmentWithConfig(matchingKey, bucketingKey, splitName, attributes, evaluationOptions); return Collections.singletonMap(splitName, getSplitResultMap(treatment)); } @@ -302,8 +316,9 @@ private Map> getTreatmentsWithConfig( String matchingKey, String bucketingKey, List splitNames, - Map attributes) { - Map treatmentsWithConfig = mSplitWrapper.getTreatmentsWithConfig(matchingKey, bucketingKey, splitNames, attributes); + Map attributes, + EvaluationOptions evaluationOptions) { + Map treatmentsWithConfig = mSplitWrapper.getTreatmentsWithConfig(matchingKey, bucketingKey, splitNames, attributes, evaluationOptions); return mapToSplitResults(treatmentsWithConfig); } @@ -311,24 +326,27 @@ private Map getTreatmentsByFlagSet( String matchingKey, String bucketingKey, String flagSet, - Map attributes) { - return mSplitWrapper.getTreatmentsByFlagSet(matchingKey, bucketingKey, flagSet, attributes); + Map attributes, + EvaluationOptions evaluationOptions) { + return mSplitWrapper.getTreatmentsByFlagSet(matchingKey, bucketingKey, flagSet, attributes, evaluationOptions); } private Map getTreatmentsByFlagSets( String matchingKey, String bucketingKey, List flagSets, - Map attributes) { - return mSplitWrapper.getTreatmentsByFlagSets(matchingKey, bucketingKey, flagSets, attributes); + Map attributes, + EvaluationOptions evaluationOptions) { + return mSplitWrapper.getTreatmentsByFlagSets(matchingKey, bucketingKey, flagSets, attributes, evaluationOptions); } private Map> getTreatmentsWithConfigByFlagSet( String matchingKey, String bucketingKey, String flagSet, - Map attributes) { - Map treatmentsWithConfig = mSplitWrapper.getTreatmentsWithConfigByFlagSet(matchingKey, bucketingKey, flagSet, attributes); + Map attributes, + EvaluationOptions evaluationOptions) { + Map treatmentsWithConfig = mSplitWrapper.getTreatmentsWithConfigByFlagSet(matchingKey, bucketingKey, flagSet, attributes, evaluationOptions); return mapToSplitResults(treatmentsWithConfig); } @@ -336,8 +354,9 @@ private Map> getTreatmentsWithConfigByFlagSets( String matchingKey, String bucketingKey, List flagSets, - Map attributes) { - Map treatmentsWithConfig = mSplitWrapper.getTreatmentsWithConfigByFlagSets(matchingKey, bucketingKey, flagSets, attributes); + Map attributes, + EvaluationOptions evaluationOptions) { + Map treatmentsWithConfig = mSplitWrapper.getTreatmentsWithConfigByFlagSets(matchingKey, bucketingKey, flagSets, attributes, evaluationOptions); return mapToSplitResults(treatmentsWithConfig); } @@ -474,7 +493,31 @@ private static Map getSplitViewAsMap(@Nullable SplitView splitVi splitViewMap.put("configs", splitView.configs); splitViewMap.put("defaultTreatment", splitView.defaultTreatment); splitViewMap.put("sets", splitView.sets); + splitViewMap.put("prerequisites", getPrerequisiteMap(splitView.prerequisites)); + splitViewMap.put("impressionsDisabled", splitView.impressionsDisabled); return splitViewMap; } + + private static List> getPrerequisiteMap(List prerequisites) { + List> prerequisiteList = new ArrayList<>(); + + for (Prerequisite prerequisite : prerequisites) { + Map prerequisiteMap = new HashMap<>(); + prerequisiteMap.put("n", prerequisite.getFlagName()); + prerequisiteMap.put("t", prerequisite.getTreatments()); + prerequisiteList.add(prerequisiteMap); + } + + return prerequisiteList; + } + + @Nullable + private EvaluationOptions buildEvaluationOptions(Map properties) { + if (properties == null || properties.isEmpty()) { + return null; + } + + return new EvaluationOptions(properties); + } } diff --git a/splitio_android/android/src/main/java/io/split/splitio/SplitWrapperImpl.java b/splitio_android/android/src/main/java/io/split/splitio/SplitWrapperImpl.java index 1adef4e..83be8bb 100644 --- a/splitio_android/android/src/main/java/io/split/splitio/SplitWrapperImpl.java +++ b/splitio_android/android/src/main/java/io/split/splitio/SplitWrapperImpl.java @@ -11,6 +11,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import io.split.android.client.EvaluationOptions; import io.split.android.client.SplitClient; import io.split.android.client.SplitFactory; import io.split.android.client.SplitResult; @@ -78,17 +79,17 @@ public boolean track(String matchingKey, @Nullable String bucketingKey, String e } @Override - public String getTreatment(String matchingKey, String bucketingKey, String splitName, Map attributes) { + public String getTreatment(String matchingKey, String bucketingKey, String splitName, Map attributes, EvaluationOptions evaluationOptions) { SplitClient client = getInitializedClient(matchingKey, bucketingKey); if (client == null) { return Treatments.CONTROL; } - return client.getTreatment(splitName, attributes); + return client.getTreatment(splitName, attributes, evaluationOptions); } @Override - public Map getTreatments(String matchingKey, String bucketingKey, List splitNames, Map attributes) { + public Map getTreatments(String matchingKey, String bucketingKey, List splitNames, Map attributes, EvaluationOptions evaluationOptions) { SplitClient client = getInitializedClient(matchingKey, bucketingKey); if (client == null) { Map defaultResult = new HashMap<>(); @@ -99,21 +100,21 @@ public Map getTreatments(String matchingKey, String bucketingKey return defaultResult; } - return client.getTreatments(splitNames, attributes); + return client.getTreatments(splitNames, attributes, evaluationOptions); } @Override - public SplitResult getTreatmentWithConfig(String matchingKey, String bucketingKey, String splitName, Map attributes) { + public SplitResult getTreatmentWithConfig(String matchingKey, String bucketingKey, String splitName, Map attributes, EvaluationOptions evaluationOptions) { SplitClient client = getInitializedClient(matchingKey, bucketingKey); if (client == null) { return new SplitResult(Treatments.CONTROL); } - return client.getTreatmentWithConfig(splitName, attributes); + return client.getTreatmentWithConfig(splitName, attributes, evaluationOptions); } @Override - public Map getTreatmentsWithConfig(String matchingKey, String bucketingKey, List splitNames, Map attributes) { + public Map getTreatmentsWithConfig(String matchingKey, String bucketingKey, List splitNames, Map attributes, EvaluationOptions evaluationOptions) { SplitClient client = getInitializedClient(matchingKey, bucketingKey); if (client == null) { Map defaultResult = new HashMap<>(); @@ -124,47 +125,47 @@ public Map getTreatmentsWithConfig(String matchingKey, Stri return defaultResult; } - return client.getTreatmentsWithConfig(splitNames, attributes); + return client.getTreatmentsWithConfig(splitNames, attributes, evaluationOptions); } @Override - public Map getTreatmentsByFlagSet(String matchingKey, String bucketingKey, String flagSet, Map attributes) { + public Map getTreatmentsByFlagSet(String matchingKey, String bucketingKey, String flagSet, Map attributes, EvaluationOptions evaluationOptions) { SplitClient client = getInitializedClient(matchingKey, bucketingKey); if (client == null) { return new HashMap<>(); } - return client.getTreatmentsByFlagSet(flagSet, attributes); + return client.getTreatmentsByFlagSet(flagSet, attributes, evaluationOptions); } @Override - public Map getTreatmentsByFlagSets(String matchingKey, String bucketingKey, List flagSets, Map attributes) { + public Map getTreatmentsByFlagSets(String matchingKey, String bucketingKey, List flagSets, Map attributes, EvaluationOptions evaluationOptions) { SplitClient client = getInitializedClient(matchingKey, bucketingKey); if (client == null) { return new HashMap<>(); } - return client.getTreatmentsByFlagSets(flagSets, attributes); + return client.getTreatmentsByFlagSets(flagSets, attributes, evaluationOptions); } @Override - public Map getTreatmentsWithConfigByFlagSet(String matchingKey, String bucketingKey, String flagSet, Map attributes) { + public Map getTreatmentsWithConfigByFlagSet(String matchingKey, String bucketingKey, String flagSet, Map attributes, EvaluationOptions evaluationOptions) { SplitClient client = getInitializedClient(matchingKey, bucketingKey); if (client == null) { return new HashMap<>(); } - return client.getTreatmentsWithConfigByFlagSet(flagSet, attributes); + return client.getTreatmentsWithConfigByFlagSet(flagSet, attributes, evaluationOptions); } @Override - public Map getTreatmentsWithConfigByFlagSets(String matchingKey, String bucketingKey, List flagSets, Map attributes) { + public Map getTreatmentsWithConfigByFlagSets(String matchingKey, String bucketingKey, List flagSets, Map attributes, EvaluationOptions evaluationOptions) { SplitClient client = getInitializedClient(matchingKey, bucketingKey); if (client == null) { return new HashMap<>(); } - return client.getTreatmentsWithConfigByFlagSets(flagSets, attributes); + return client.getTreatmentsWithConfigByFlagSets(flagSets, attributes, evaluationOptions); } @Override diff --git a/splitio_android/android/src/test/java/io/split/splitio/ImpressionListenerImpTest.java b/splitio_android/android/src/test/java/io/split/splitio/ImpressionListenerImpTest.java index 69be7b9..91b624c 100644 --- a/splitio_android/android/src/test/java/io/split/splitio/ImpressionListenerImpTest.java +++ b/splitio_android/android/src/test/java/io/split/splitio/ImpressionListenerImpTest.java @@ -31,7 +31,7 @@ public void setUp() { @Test public void loggingInvokesMethodOnMethodChannel() { - Impression impression = new Impression("key", null, "my_split", "on", 20021002, "on treatment", 1002L, Collections.emptyMap()); + Impression impression = new Impression("key", null, "my_split", "on", 20021002, "on treatment", 1002L, Collections.emptyMap(), "[{\"prop1\", \"value1\"}, {\"prop2\", \"value2\"}]"); mImpressionListener.log(impression); verify(mMethodChannel).invokeMethod(eq("impressionLog"), any()); @@ -39,7 +39,7 @@ public void loggingInvokesMethodOnMethodChannel() { @Test public void loggingInvokesMethodOnMethodChannelWithCorrectArgument() { - Impression impression = new Impression("key", null, "my_split", "on", 20021002, "on treatment", 1002L, Collections.singletonMap("age", 25)); + Impression impression = new Impression("key", null, "my_split", "on", 20021002, "on treatment", 1002L, Collections.singletonMap("age", 25), "[{\"prop1\", \"value1\"}, {\"prop2\", \"value2\"}]"); Map expectedImpressionMap = new HashMap<>(); expectedImpressionMap.put("key", "key"); expectedImpressionMap.put("bucketingKey", null); @@ -49,6 +49,7 @@ public void loggingInvokesMethodOnMethodChannelWithCorrectArgument() { expectedImpressionMap.put("appliedRule", "on treatment"); expectedImpressionMap.put("changeNumber", 1002L); expectedImpressionMap.put("attributes", Collections.singletonMap("age", 25)); + expectedImpressionMap.put("properties", "[{\"prop1\", \"value1\"}, {\"prop2\", \"value2\"}]"); mImpressionListener.log(impression); verify(mMethodChannel).invokeMethod("impressionLog", expectedImpressionMap); diff --git a/splitio_android/android/src/test/java/io/split/splitio/SplitClientConfigHelperTest.java b/splitio_android/android/src/test/java/io/split/splitio/SplitClientConfigHelperTest.java index 18691f0..23a5215 100644 --- a/splitio_android/android/src/test/java/io/split/splitio/SplitClientConfigHelperTest.java +++ b/splitio_android/android/src/test/java/io/split/splitio/SplitClientConfigHelperTest.java @@ -219,4 +219,20 @@ public void certificatePinningConfigurationValuesAreMappedCorrectly() { Set host2Pins = actualConfig.getPins().get("host2"); assertEquals("sha256", host2Pins.iterator().next().getAlgorithm()); } + + @Test + public void rolloutCacheConfigurationValuesAreMappedCorrectly() { + Map configValues = new HashMap<>(); + Map rolloutCacheConfigValues = new HashMap<>(); + + rolloutCacheConfigValues.put("expirationDays", 5); + rolloutCacheConfigValues.put("clearOnInit", true); + configValues.put("rolloutCacheConfiguration", rolloutCacheConfigValues); + + SplitClientConfig splitClientConfig = SplitClientConfigHelper + .fromMap(configValues, mock(ImpressionListener.class)); + + assertEquals(5, splitClientConfig.rolloutCacheConfiguration().getExpirationDays()); + assertTrue(splitClientConfig.rolloutCacheConfiguration().isClearOnInit()); + } } diff --git a/splitio_android/android/src/test/java/io/split/splitio/SplitMethodParserImplTest.java b/splitio_android/android/src/test/java/io/split/splitio/SplitMethodParserImplTest.java index 7f7a225..d4e804a 100644 --- a/splitio_android/android/src/test/java/io/split/splitio/SplitMethodParserImplTest.java +++ b/splitio_android/android/src/test/java/io/split/splitio/SplitMethodParserImplTest.java @@ -1,5 +1,7 @@ package io.split.splitio; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; @@ -8,19 +10,29 @@ import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; +import androidx.annotation.NonNull; + import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; import io.flutter.plugin.common.MethodChannel; +import io.split.android.client.EvaluationOptions; import io.split.android.client.SplitClient; import io.split.android.client.SplitResult; +import io.split.android.client.api.SplitView; +import io.split.android.client.dtos.Prerequisite; public class SplitMethodParserImplTest { @@ -55,7 +67,7 @@ public void successfulGetClient() { mMethodParser.onMethodCall("getClient", map, mResult); verify(mResult).success(null); - verify(mSplitWrapper).getClient("user-key", "bucketing-key"); + verify(mSplitWrapper).getClient(eq("user-key"), eq("bucketing-key")); } @Test @@ -127,11 +139,13 @@ public void getTreatmentWorksCorrectly() { when(mArgumentParser.getStringArgument("bucketingKey", map)).thenReturn(null); when(mArgumentParser.getStringArgument("splitName", map)).thenReturn("split-name"); when(mArgumentParser.getMapArgument("attributes", map)).thenReturn(Collections.singletonMap("age", 10)); - when(mSplitWrapper.getTreatment(any(), any(), any(), any())).thenReturn("on"); + when(mArgumentParser.getMapArgument("evaluationOptions", map)).thenReturn(Collections.singletonMap("boolean", true)); + when(mSplitWrapper.getTreatment(any(), any(), any(), any(), any())).thenReturn("on"); mMethodParser.onMethodCall("getTreatment", map, mResult); - verify(mSplitWrapper).getTreatment("user-key", null, "split-name", Collections.singletonMap("age", 10)); + verify(mSplitWrapper).getTreatment(eq("user-key"), eq((String) null), eq("split-name"), eq(Collections.singletonMap("age", 10)), + argThat(evaluationOptionsPropertiesMatcher())); verify(mResult).success("on"); } @@ -150,11 +164,13 @@ public void getTreatmentsWorksCorrectly() { when(mArgumentParser.getStringArgument("bucketingKey", map)).thenReturn(null); when(mArgumentParser.getStringListArgument("splitName", map)).thenReturn(Arrays.asList("split1", "split2")); when(mArgumentParser.getMapArgument("attributes", map)).thenReturn(Collections.singletonMap("age", 10)); - when(mSplitWrapper.getTreatments(any(), any(), any(), any())).thenReturn(expectedResponse); + when(mArgumentParser.getMapArgument("evaluationOptions", map)).thenReturn(Collections.singletonMap("boolean", true)); + when(mSplitWrapper.getTreatments(any(), any(), any(), any(), any())).thenReturn(expectedResponse); mMethodParser.onMethodCall("getTreatments", map, mResult); - verify(mSplitWrapper).getTreatments("user-key", null, Arrays.asList("split1", "split2"), Collections.singletonMap("age", 10)); + verify(mSplitWrapper).getTreatments(eq("user-key"), eq((String) null), eq(Arrays.asList("split1", "split2")), eq(Collections.singletonMap("age", 10)), + argThat(evaluationOptionsPropertiesMatcher())); verify(mResult).success(expectedResponse); } @@ -184,11 +200,13 @@ public void getTreatmentsWithConfigWorksCorrectly() { when(mArgumentParser.getStringArgument("bucketingKey", map)).thenReturn("bucketing-key"); when(mArgumentParser.getStringListArgument("splitName", map)).thenReturn(Arrays.asList("split1", "split2")); when(mArgumentParser.getMapArgument("attributes", map)).thenReturn(Collections.singletonMap("age", 10)); - when(mSplitWrapper.getTreatmentsWithConfig(any(), any(), any(), any())).thenReturn(mockResult); + when(mArgumentParser.getMapArgument("evaluationOptions", map)).thenReturn(Collections.singletonMap("boolean", true)); + when(mSplitWrapper.getTreatmentsWithConfig(any(), any(), any(), any(), any())).thenReturn(mockResult); mMethodParser.onMethodCall("getTreatmentsWithConfig", map, mResult); - verify(mSplitWrapper).getTreatmentsWithConfig("user-key", "bucketing-key", Arrays.asList("split1", "split2"), Collections.singletonMap("age", 10)); + verify(mSplitWrapper).getTreatmentsWithConfig(eq("user-key"), eq("bucketing-key"), eq(Arrays.asList("split1", "split2")), eq(Collections.singletonMap("age", 10)), + argThat(evaluationOptionsPropertiesMatcher())); verify(mResult).success(finalResultMap); } @@ -204,11 +222,13 @@ public void getTreatmentWithConfigWorksCorrectly() { when(mArgumentParser.getStringArgument("bucketingKey", map)).thenReturn("bucketing-key"); when(mArgumentParser.getStringArgument("splitName", map)).thenReturn("split-name"); when(mArgumentParser.getMapArgument("attributes", map)).thenReturn(Collections.singletonMap("age", 10)); - when(mSplitWrapper.getTreatmentWithConfig(any(), any(), any(), any())).thenReturn(new SplitResult("on", "{config}")); + when(mArgumentParser.getMapArgument("evaluationOptions", map)).thenReturn(Collections.singletonMap("boolean", true)); + when(mSplitWrapper.getTreatmentWithConfig(any(), any(), any(), any(), any())).thenReturn(new SplitResult("on", "{config}")); mMethodParser.onMethodCall("getTreatmentWithConfig", map, mResult); - verify(mSplitWrapper).getTreatmentWithConfig("user-key", "bucketing-key", "split-name", Collections.singletonMap("age", 10)); + verify(mSplitWrapper).getTreatmentWithConfig(eq("user-key"), eq("bucketing-key"), eq("split-name"), eq(Collections.singletonMap("age", 10)), + argThat(evaluationOptionsPropertiesMatcher())); Map resultMap = new HashMap<>(); resultMap.put("treatment", "on"); resultMap.put("config", "{config}"); @@ -231,7 +251,7 @@ public void trackWithValue() { mMethodParser.onMethodCall("track", map, mResult); - verify(mSplitWrapper).track("user-key", "bucketing-key", "my-event", null, 25.20, Collections.emptyMap()); + verify(mSplitWrapper).track(eq("user-key"), eq("bucketing-key"), eq("my-event"), eq((String) null), eq(25.20), eq(Collections.emptyMap())); } @Test @@ -249,7 +269,7 @@ public void trackWithInvalidValue() { mMethodParser.onMethodCall("track", map, mResult); - verify(mSplitWrapper).track("user-key", "bucketing-key", "my-event", null, null, Collections.emptyMap()); + verify(mSplitWrapper).track(eq("user-key"), eq("bucketing-key"), eq("my-event"), eq((String) null), eq((Double) null), eq(Collections.emptyMap())); } @Test @@ -269,7 +289,7 @@ public void trackWithValueAndProperties() { mMethodParser.onMethodCall("track", map, mResult); - verify(mSplitWrapper).track("user-key", "bucketing-key", "my-event", null, 25.20, Collections.singletonMap("age", 50)); + verify(mSplitWrapper).track(eq("user-key"), eq("bucketing-key"), eq("my-event"), eq((String) null), eq(25.20), eq(Collections.singletonMap("age", 50))); } @Test @@ -291,7 +311,7 @@ public void trackWithEverything() { mMethodParser.onMethodCall("track", map, mResult); - verify(mSplitWrapper).track("user-key", "bucketing-key", "my-event", "account", 25.20, Collections.singletonMap("age", 50)); + verify(mSplitWrapper).track(eq("user-key"), eq("bucketing-key"), eq("my-event"), eq("account"), eq(25.20), eq(Collections.singletonMap("age", 50))); } @Test @@ -307,7 +327,7 @@ public void getSingleAttribute() { mMethodParser.onMethodCall("getAttribute", map, mResult); - verify(mSplitWrapper).getAttribute("user-key", "bucketing-key", "my_attribute"); + verify(mSplitWrapper).getAttribute(eq("user-key"), eq("bucketing-key"), eq("my_attribute")); } @Test @@ -321,7 +341,7 @@ public void getAllAttributes() { mMethodParser.onMethodCall("getAllAttributes", map, mResult); - verify(mSplitWrapper).getAllAttributes("user-key", "bucketing-key"); + verify(mSplitWrapper).getAllAttributes(eq("user-key"), eq("bucketing-key")); } @Test @@ -339,7 +359,7 @@ public void setSingleAttribute() { mMethodParser.onMethodCall("setAttribute", map, mResult); - verify(mSplitWrapper).setAttribute("user-key", "bucketing-key", "my_attr", "attr_value"); + verify(mSplitWrapper).setAttribute(eq("user-key"), eq("bucketing-key"), eq("my_attr"), eq("attr_value")); } @Test @@ -361,7 +381,7 @@ public void setMultipleAttributes() { mMethodParser.onMethodCall("setAttributes", map, mResult); - verify(mSplitWrapper).setAttributes("user-key", "bucketing-key", attributesMap); + verify(mSplitWrapper).setAttributes(eq("user-key"), eq("bucketing-key"), eq(attributesMap)); } @Test @@ -377,7 +397,7 @@ public void removeAttribute() { mMethodParser.onMethodCall("removeAttribute", map, mResult); - verify(mSplitWrapper).removeAttribute("user-key", "bucketing-key", "my_attr"); + verify(mSplitWrapper).removeAttribute(eq("user-key"), eq("bucketing-key"), eq("my_attr")); } @Test @@ -391,7 +411,7 @@ public void clearAttributes() { mMethodParser.onMethodCall("clearAttributes", map, mResult); - verify(mSplitWrapper).clearAttributes("user-key", "bucketing-key"); + verify(mSplitWrapper).clearAttributes(eq("user-key"), eq("bucketing-key")); } @Test @@ -405,7 +425,7 @@ public void flush() { mMethodParser.onMethodCall("flush", map, mResult); - verify(mSplitWrapper).flush("user-key", "bucketing-key"); + verify(mSplitWrapper).flush(eq("user-key"), eq("bucketing-key")); verify(mResult).success(null); } @@ -420,22 +440,89 @@ public void destroy() { mMethodParser.onMethodCall("destroy", map, mResult); - verify(mSplitWrapper).destroy("user-key", "bucketing-key"); + verify(mSplitWrapper).destroy(eq("user-key"), eq("bucketing-key")); verify(mResult).success(null); } @Test - public void splitNames() { - mMethodParser.onMethodCall("splitNames", Collections.emptyMap(), mResult); - - verify(mSplitWrapper).splitNames(); + public void splitViews() { + Prerequisite prerequisite1 = mock(Prerequisite.class); + when(prerequisite1.getFlagName()).thenReturn("flag1"); + when(prerequisite1.getTreatments()).thenReturn(new HashSet<>(Arrays.asList("on", "true"))); + + Prerequisite prerequisite2 = mock(Prerequisite.class); + when(prerequisite2.getFlagName()).thenReturn("flag2"); + when(prerequisite2.getTreatments()).thenReturn(new HashSet<>(Arrays.asList("on", "true"))); + + List prerequisites = Arrays.asList(prerequisite1, prerequisite2); + + SplitView splitView = mock(SplitView.class); + splitView.name = "test_split"; + splitView.trafficType = "user"; + splitView.killed = false; + splitView.treatments = Arrays.asList("on", "off"); + splitView.changeNumber = 123L; + splitView.configs = Collections.singletonMap("on", "{\"color\": \"blue\"}"); + splitView.defaultTreatment = "off"; + splitView.sets = Arrays.asList("set1", "set2"); + splitView.prerequisites = prerequisites; + splitView.impressionsDisabled = true; + + when(mSplitWrapper.splits()).thenReturn(Arrays.asList(splitView)); + + mMethodParser.onMethodCall("splits", Collections.emptyMap(), mResult); + + verify(mSplitWrapper).splits(); + + // Verify that the result includes the correctly formatted prerequisites + ArgumentCaptor>> captor = ArgumentCaptor.forClass(List.class); + verify(mResult).success(captor.capture()); + + List> result = captor.getValue(); + assertEquals(1, result.size()); + + Map splitViewMap = result.get(0); + + // Verify all SplitView fields are correctly mapped + assertEquals("test_split", splitViewMap.get("name")); + assertEquals("user", splitViewMap.get("trafficType")); + assertEquals(false, splitViewMap.get("killed")); + assertEquals(Arrays.asList("on", "off"), splitViewMap.get("treatments")); + assertEquals(123L, splitViewMap.get("changeNumber")); + assertEquals(Collections.singletonMap("on", "{\"color\": \"blue\"}"), splitViewMap.get("configs")); + assertEquals("off", splitViewMap.get("defaultTreatment")); + assertEquals(Arrays.asList("set1", "set2"), splitViewMap.get("sets")); + assertEquals(true, splitViewMap.get("impressionsDisabled")); + + // Verify prerequisites + assertTrue(splitViewMap.containsKey("prerequisites")); + + @SuppressWarnings("unchecked") + List> prerequisitesResult = (List>) splitViewMap.get("prerequisites"); + assertEquals(2, prerequisitesResult.size()); + + // Verify first prerequisite + Map prereq1 = prerequisitesResult.get(0); + assertEquals("flag1", prereq1.get("n")); + Set treatments = (Set) prereq1.get("t"); + assertEquals(2, treatments.size()); + assertTrue(treatments.contains("on")); + assertTrue(treatments.contains("true")); + + // Verify second prerequisite + Map prereq2 = prerequisitesResult.get(1); + assertEquals("flag2", prereq2.get("n")); + Set treatments1 = (Set) prereq2.get("t"); + assertEquals(2, treatments1.size()); + assertTrue(treatments1.contains("on")); + assertTrue(treatments1.contains("true")); } @Test - public void splits() { - mMethodParser.onMethodCall("splits", Collections.emptyMap(), mResult); + public void splitNames() { + mMethodParser.onMethodCall("splitNames", Collections.emptyMap(), mResult); - verify(mSplitWrapper).splits(); + verify(mSplitWrapper).splitNames(); } @Test @@ -444,7 +531,7 @@ public void split() { mMethodParser.onMethodCall("split", Collections.singletonMap("splitName", "my_split"), mResult); - verify(mSplitWrapper).split("my_split"); + verify(mSplitWrapper).split(eq("my_split")); } @Test @@ -492,7 +579,7 @@ public void setUserConsentEnabled() { mMethodParser.onMethodCall("setUserConsent", Collections.singletonMap("value", true), mResult); verify(mResult).success(null); - verify(mSplitWrapper).setUserConsent(true); + verify(mSplitWrapper).setUserConsent(eq(true)); } @Test @@ -502,7 +589,7 @@ public void setUserConsentDisabled() { mMethodParser.onMethodCall("setUserConsent", Collections.singletonMap("value", false), mResult); verify(mResult).success(null); - verify(mSplitWrapper).setUserConsent(false); + verify(mSplitWrapper).setUserConsent(eq(false)); } @Test @@ -517,11 +604,13 @@ public void getTreatmentsByFlagSetWorksCorrectly() { when(mArgumentParser.getStringArgument("bucketingKey", map)).thenReturn("bucketing-key"); when(mArgumentParser.getStringArgument("flagSet", map)).thenReturn("set_1"); when(mArgumentParser.getMapArgument("attributes", map)).thenReturn(Collections.singletonMap("age", 10)); - when(mSplitWrapper.getTreatmentsByFlagSet(any(), any(), any(), any())).thenReturn(Collections.singletonMap("flag_1", "on")); + when(mArgumentParser.getMapArgument("evaluationOptions", map)).thenReturn(Collections.singletonMap("boolean", true)); + when(mSplitWrapper.getTreatmentsByFlagSet(any(), any(), any(), any(), any())).thenReturn(Collections.singletonMap("flag_1", "on")); mMethodParser.onMethodCall("getTreatmentsByFlagSet", map, mResult); - verify(mSplitWrapper).getTreatmentsByFlagSet("user-key", "bucketing-key", "set_1", Collections.singletonMap("age", 10)); + verify(mSplitWrapper).getTreatmentsByFlagSet(eq("user-key"), eq("bucketing-key"), eq("set_1"), eq(Collections.singletonMap("age", 10)), + argThat(evaluationOptionsPropertiesMatcher())); verify(mResult).success(Collections.singletonMap("flag_1", "on")); } @@ -537,11 +626,13 @@ public void getTreatmentsByFlagSetsWorksCorrectly() { when(mArgumentParser.getStringArgument("bucketingKey", map)).thenReturn("bucketing-key"); when(mArgumentParser.getStringListArgument("flagSets", map)).thenReturn(Arrays.asList("set_1", "set_2")); when(mArgumentParser.getMapArgument("attributes", map)).thenReturn(Collections.singletonMap("age", 10)); - when(mSplitWrapper.getTreatmentsByFlagSets(any(), any(), any(), any())).thenReturn(Collections.singletonMap("flag_1", "on")); + when(mArgumentParser.getMapArgument("evaluationOptions", map)).thenReturn(Collections.singletonMap("boolean", true)); + when(mSplitWrapper.getTreatmentsByFlagSets(any(), any(), any(), any(), any())).thenReturn(Collections.singletonMap("flag_1", "on")); mMethodParser.onMethodCall("getTreatmentsByFlagSets", map, mResult); - verify(mSplitWrapper).getTreatmentsByFlagSets("user-key", "bucketing-key", Arrays.asList("set_1", "set_2"), Collections.singletonMap("age", 10)); + verify(mSplitWrapper).getTreatmentsByFlagSets(eq("user-key"), eq("bucketing-key"), eq(Arrays.asList("set_1", "set_2")), eq(Collections.singletonMap("age", 10)), + argThat(evaluationOptionsPropertiesMatcher())); verify(mResult).success(Collections.singletonMap("flag_1", "on")); } @@ -561,11 +652,13 @@ public void getTreatmentsWithConfigByFlagSetWorksCorrectly() { when(mArgumentParser.getStringArgument("bucketingKey", map)).thenReturn("bucketing-key"); when(mArgumentParser.getStringArgument("flagSet", map)).thenReturn("set_1"); when(mArgumentParser.getMapArgument("attributes", map)).thenReturn(Collections.singletonMap("age", 10)); - when(mSplitWrapper.getTreatmentsWithConfigByFlagSet(any(), any(), any(), any())).thenReturn(Collections.singletonMap("flag_1", new SplitResult("on", "{config}"))); + when(mArgumentParser.getMapArgument("evaluationOptions", map)).thenReturn(Collections.singletonMap("boolean", true)); + when(mSplitWrapper.getTreatmentsWithConfigByFlagSet(any(), any(), any(), any(), any())).thenReturn(Collections.singletonMap("flag_1", new SplitResult("on", "{config}"))); mMethodParser.onMethodCall("getTreatmentsWithConfigByFlagSet", map, mResult); - verify(mSplitWrapper).getTreatmentsWithConfigByFlagSet("user-key", "bucketing-key", "set_1", Collections.singletonMap("age", 10)); + verify(mSplitWrapper).getTreatmentsWithConfigByFlagSet(eq("user-key"), eq("bucketing-key"), eq("set_1"), eq(Collections.singletonMap("age", 10)), + argThat(evaluationOptionsPropertiesMatcher())); verify(mResult).success(Collections.singletonMap("flag_1", resultMap1)); } @@ -585,11 +678,18 @@ public void getTreatmentsWithConfigByFlagSetsWorksCorrectly() { when(mArgumentParser.getStringArgument("bucketingKey", map)).thenReturn("bucketing-key"); when(mArgumentParser.getStringListArgument("flagSets", map)).thenReturn(Arrays.asList("set_1", "set_2")); when(mArgumentParser.getMapArgument("attributes", map)).thenReturn(Collections.singletonMap("age", 10)); - when(mSplitWrapper.getTreatmentsWithConfigByFlagSets(any(), any(), any(), any())).thenReturn(Collections.singletonMap("flag_1", new SplitResult("on", "{config}"))); + when(mArgumentParser.getMapArgument("evaluationOptions", map)).thenReturn(Collections.singletonMap("boolean", true)); + when(mSplitWrapper.getTreatmentsWithConfigByFlagSets(any(), any(), any(), any(), any())).thenReturn(Collections.singletonMap("flag_1", new SplitResult("on", "{config}"))); mMethodParser.onMethodCall("getTreatmentsWithConfigByFlagSets", map, mResult); - verify(mSplitWrapper).getTreatmentsWithConfigByFlagSets("user-key", "bucketing-key", Arrays.asList("set_1", "set_2"), Collections.singletonMap("age", 10)); + verify(mSplitWrapper).getTreatmentsWithConfigByFlagSets(eq("user-key"), eq("bucketing-key"), eq(Arrays.asList("set_1", "set_2")), eq(Collections.singletonMap("age", 10)), + argThat(evaluationOptionsPropertiesMatcher())); verify(mResult).success(Collections.singletonMap("flag_1", resultMap1)); } + + @NonNull + private static ArgumentMatcher evaluationOptionsPropertiesMatcher() { + return options -> options != null && options.getProperties().size() == 1 && options.getProperties().containsKey("boolean") && (Boolean) options.getProperties().get("boolean"); + } } diff --git a/splitio_android/android/src/test/java/io/split/splitio/SplitWrapperImplTest.java b/splitio_android/android/src/test/java/io/split/splitio/SplitWrapperImplTest.java index 733fb31..42ae794 100644 --- a/splitio_android/android/src/test/java/io/split/splitio/SplitWrapperImplTest.java +++ b/splitio_android/android/src/test/java/io/split/splitio/SplitWrapperImplTest.java @@ -19,6 +19,7 @@ import java.util.Map; import java.util.Set; +import io.split.android.client.EvaluationOptions; import io.split.android.client.SplitClient; import io.split.android.client.SplitFactory; import io.split.android.client.SplitManager; @@ -60,9 +61,10 @@ public void testGetTreatment() { when(mSplitFactory.client("key", null)).thenReturn(clientMock); when(mUsedKeys.contains(new Key("key", null))).thenReturn(true); - mSplitWrapper.getTreatment("key", null, "split-name", Collections.singletonMap("age", 50)); + EvaluationOptions evaluationOptions = new EvaluationOptions(Collections.singletonMap("boolean", true)); + mSplitWrapper.getTreatment("key", null, "split-name", Collections.singletonMap("age", 50), evaluationOptions); - verify(clientMock).getTreatment("split-name", Collections.singletonMap("age", 50)); + verify(clientMock).getTreatment("split-name", Collections.singletonMap("age", 50), evaluationOptions); } @Test @@ -72,9 +74,10 @@ public void testGetTreatments() { when(mSplitFactory.client("key", null)).thenReturn(clientMock); when(mUsedKeys.contains(new Key("key", null))).thenReturn(true); - mSplitWrapper.getTreatments("key", null, Arrays.asList("split1", "split2"), Collections.singletonMap("age", 50)); + EvaluationOptions evaluationOptions = new EvaluationOptions(Collections.singletonMap("boolean", true)); + mSplitWrapper.getTreatments("key", null, Arrays.asList("split1", "split2"), Collections.singletonMap("age", 50), evaluationOptions); - verify(clientMock).getTreatments(Arrays.asList("split1", "split2"), Collections.singletonMap("age", 50)); + verify(clientMock).getTreatments(Arrays.asList("split1", "split2"), Collections.singletonMap("age", 50), evaluationOptions); } @Test @@ -84,9 +87,10 @@ public void testGetTreatmentWithConfig() { when(mSplitFactory.client("key", null)).thenReturn(clientMock); when(mUsedKeys.contains(new Key("key", null))).thenReturn(true); - mSplitWrapper.getTreatmentWithConfig("key", null, "split-name", Collections.singletonMap("age", 50)); + EvaluationOptions evaluationOptions = new EvaluationOptions(Collections.singletonMap("boolean", true)); + mSplitWrapper.getTreatmentWithConfig("key", null, "split-name", Collections.singletonMap("age", 50), evaluationOptions); - verify(clientMock).getTreatmentWithConfig("split-name", Collections.singletonMap("age", 50)); + verify(clientMock).getTreatmentWithConfig("split-name", Collections.singletonMap("age", 50), evaluationOptions); } @Test @@ -96,9 +100,10 @@ public void testGetTreatmentsWithConfig() { when(mSplitFactory.client("key", null)).thenReturn(clientMock); when(mUsedKeys.contains(new Key("key", null))).thenReturn(true); - mSplitWrapper.getTreatmentsWithConfig("key", null, Arrays.asList("split1", "split2"), Collections.singletonMap("age", 50)); + EvaluationOptions evaluationOptions = new EvaluationOptions(Collections.singletonMap("boolean", true)); + mSplitWrapper.getTreatmentsWithConfig("key", null, Arrays.asList("split1", "split2"), Collections.singletonMap("age", 50), evaluationOptions); - verify(clientMock).getTreatmentsWithConfig(Arrays.asList("split1", "split2"), Collections.singletonMap("age", 50)); + verify(clientMock).getTreatmentsWithConfig(Arrays.asList("split1", "split2"), Collections.singletonMap("age", 50), evaluationOptions); } @Test @@ -108,9 +113,10 @@ public void testGetTreatmentsByFlagSet() { when(mUsedKeys.contains(new Key("key", null))).thenReturn(true); Map attrs = Collections.singletonMap("age", 50); - mSplitWrapper.getTreatmentsByFlagSet("key", null, "flag-set", attrs); + EvaluationOptions evaluationOptions = new EvaluationOptions(Collections.singletonMap("boolean", true)); + mSplitWrapper.getTreatmentsByFlagSet("key", null, "flag-set", attrs, evaluationOptions); - verify(clientMock).getTreatmentsByFlagSet("flag-set", attrs); + verify(clientMock).getTreatmentsByFlagSet("flag-set", attrs, evaluationOptions); } @Test @@ -121,9 +127,10 @@ public void testGetTreatmentsByFlagSets() { Map attrs = Collections.singletonMap("age", 50); List sets = Arrays.asList("set_1", "set_2"); - mSplitWrapper.getTreatmentsByFlagSets("key", null, sets, attrs); + EvaluationOptions evaluationOptions = new EvaluationOptions(Collections.singletonMap("boolean", true)); + mSplitWrapper.getTreatmentsByFlagSets("key", null, sets, attrs, evaluationOptions); - verify(clientMock).getTreatmentsByFlagSets(sets, attrs); + verify(clientMock).getTreatmentsByFlagSets(sets, attrs, evaluationOptions); } @Test @@ -133,9 +140,10 @@ public void testGetTreatmentsWithConfigByFlagSet() { when(mUsedKeys.contains(new Key("key", null))).thenReturn(true); Map attrs = Collections.singletonMap("age", 50); - mSplitWrapper.getTreatmentsWithConfigByFlagSet("key", null,"set_1", attrs); + EvaluationOptions evaluationOptions = new EvaluationOptions(Collections.singletonMap("boolean", true)); + mSplitWrapper.getTreatmentsWithConfigByFlagSet("key", null,"set_1", attrs, evaluationOptions); - verify(clientMock).getTreatmentsWithConfigByFlagSet("set_1", attrs); + verify(clientMock).getTreatmentsWithConfigByFlagSet("set_1", attrs, evaluationOptions); } @Test @@ -146,9 +154,10 @@ public void testGetTreatmentsWithConfigByFlagSets() { Map attrs = Collections.singletonMap("age", 50); List sets = Arrays.asList("set_1", "set_2"); - mSplitWrapper.getTreatmentsWithConfigByFlagSets("key", null, sets, attrs); + EvaluationOptions evaluationOptions = new EvaluationOptions(Collections.singletonMap("boolean", true)); + mSplitWrapper.getTreatmentsWithConfigByFlagSets("key", null, sets, attrs, evaluationOptions); - verify(clientMock).getTreatmentsWithConfigByFlagSets(sets, attrs); + verify(clientMock).getTreatmentsWithConfigByFlagSets(sets, attrs, evaluationOptions); } @Test diff --git a/splitio_android/pubspec.yaml b/splitio_android/pubspec.yaml index cc75d6e..05d4e96 100644 --- a/splitio_android/pubspec.yaml +++ b/splitio_android/pubspec.yaml @@ -1,7 +1,7 @@ name: splitio_android description: The official Android implementation of splitio Flutter plugin. repository: https://github.com/splitio/flutter-sdk-plugin/tree/main/splitio_android -version: 0.2.0 +version: 1.0.0-rc.1 environment: sdk: ">=2.16.2 <4.0.0" @@ -19,7 +19,8 @@ flutter: dependencies: flutter: sdk: flutter - splitio_platform_interface: ^1.5.0 + splitio_platform_interface: # ^2.0.0-rc.1 + path: ../splitio_platform_interface dev_dependencies: flutter_test: diff --git a/splitio_android/test/splitio_android_test.dart b/splitio_android/test/splitio_android_test.dart index 554d4cd..7cecd3d 100644 --- a/splitio_android/test/splitio_android_test.dart +++ b/splitio_android/test/splitio_android_test.dart @@ -502,14 +502,20 @@ void main() { apiKey: 'api-key', matchingKey: 'matching-key', bucketingKey: 'bucketing-key', - sdkConfiguration: - SplitConfiguration(logLevel: SplitLogLevel.error, streamingEnabled: false, readyTimeout: 1)); + sdkConfiguration: SplitConfiguration( + logLevel: SplitLogLevel.error, + streamingEnabled: false, + readyTimeout: 1)); expect(methodName, 'init'); expect(methodArguments, { 'apiKey': 'api-key', 'matchingKey': 'matching-key', 'bucketingKey': 'bucketing-key', - 'sdkConfiguration': {'logLevel': 'error', 'streamingEnabled': false, 'readyTimeout': 1}, + 'sdkConfiguration': { + 'logLevel': 'error', + 'streamingEnabled': false, + 'readyTimeout': 1 + }, }); }); }); @@ -631,6 +637,7 @@ void main() { expect(impression.appliedRule, 'appliedRule'); expect(impression.changeNumber, 200); expect(impression.attributes, {}); + expect(impression.properties, {}); }), ); _simulateMethodInvocation('impressionLog', key: 'matching-key', arguments: { @@ -641,7 +648,8 @@ void main() { 'time': 3000, 'appliedRule': 'appliedRule', 'changeNumber': 200, - 'attributes': {} + 'attributes': {}, + 'properties': {}, }); }); diff --git a/splitio_ios/LICENSE b/splitio_ios/LICENSE index 68e2d99..af74bff 100644 --- a/splitio_ios/LICENSE +++ b/splitio_ios/LICENSE @@ -176,7 +176,7 @@ END OF TERMS AND CONDITIONS - Copyright © 2024 Split Software, Inc. + Copyright © 2025 Split Software, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/splitio_ios/example/ios/Podfile.lock b/splitio_ios/example/ios/Podfile.lock index f1c80a0..ea8a88c 100644 --- a/splitio_ios/example/ios/Podfile.lock +++ b/splitio_ios/example/ios/Podfile.lock @@ -1,9 +1,9 @@ PODS: - Flutter (1.0.0) - - Split (3.0.0) - - splitio_ios (0.7.0): + - Split (3.3.2) + - splitio_ios (0.8.0): - Flutter - - Split (~> 3.0.0) + - Split (~> 3.3.2) DEPENDENCIES: - Flutter (from `Flutter`) @@ -21,9 +21,9 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - Split: 66424040ad573d052f58269f841e71b34578a916 - splitio_ios: e4e3becbe89cae0a2fa9ca03a575c21f23af0d90 + Split: 0d4962a6c15180e1857c1a3753e1ae9c91a6150b + splitio_ios: 438ad21d0dfe467670f8b9508773b77b16a71d6b PODFILE CHECKSUM: aed42fc5c94ade572556b7ed357c5c57f1bd83a2 -COCOAPODS: 1.15.0 +COCOAPODS: 1.16.2 diff --git a/splitio_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/splitio_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e0c9630..4300fbd 100644 --- a/splitio_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/splitio_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> diff --git a/splitio_ios/example/ios/Runner/AppDelegate.swift b/splitio_ios/example/ios/Runner/AppDelegate.swift index 70693e4..b636303 100644 --- a/splitio_ios/example/ios/Runner/AppDelegate.swift +++ b/splitio_ios/example/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import UIKit import Flutter -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/splitio_ios/example/ios/SplitTests/ExtensionsTests.swift b/splitio_ios/example/ios/SplitTests/ExtensionsTests.swift index 8cc0e67..ea87d72 100644 --- a/splitio_ios/example/ios/SplitTests/ExtensionsTests.swift +++ b/splitio_ios/example/ios/SplitTests/ExtensionsTests.swift @@ -1,5 +1,6 @@ import XCTest import Split +@testable import splitio_ios class ExtensionsTests: XCTestCase { @@ -13,9 +14,10 @@ class ExtensionsTests: XCTestCase { impression.label = "label" impression.treatment = "on" impression.time = 16161616 + impression.properties = "{\"myProp\": true}" let impressionMap = impression.toMap() - XCTAssert(impressionMap.count == 8) + XCTAssert(impressionMap.count == 9) XCTAssert(NSDictionary(dictionary: impressionMap).isEqual(to: [ "key": "matching-key", "bucketingKey": "bucketing-key", @@ -24,6 +26,45 @@ class ExtensionsTests: XCTestCase { "appliedRule": "label", "treatment": "on", "split": "my-split", - "time": 16161616])) + "time": 16161616, + "properties": "{\"myProp\": true}"])) + } + + func testSplitViewMapping() throws { + var splitView = SplitView() + splitView.name = "my-split" + splitView.trafficType = "account" + splitView.killed = true + splitView.treatments = ["on", "off"] + splitView.changeNumber = 121212 + splitView.configs = ["key": "value"] + splitView.defaultTreatment = "off" + splitView.sets = ["set1", "set2"] + + let prerequisiteJSON = """ + { + "n": "pre1", + "t": ["on", "off"] + } + """.data(using: .utf8)! + + let prerequisite = try JSONDecoder().decode(Prerequisite.self, from: prerequisiteJSON) + splitView.prerequisites = [prerequisite] + splitView.impressionsDisabled = true + + let splitViewMap = SplitView.asMap(splitView: splitView) + + XCTAssert(splitViewMap.count == 10) + XCTAssert(NSDictionary(dictionary: splitViewMap).isEqual(to: [ + "name": "my-split", + "trafficType": "account", + "killed": true, + "treatments": ["on", "off"], + "changeNumber": 121212, + "configs": ["key": "value"], + "defaultTreatment": "off", + "sets": ["set1", "set2"], + "impressionsDisabled": true, + "prerequisites": [prerequisite]])) } } diff --git a/splitio_ios/example/ios/SplitTests/SplitClientConfigHelperTests.swift b/splitio_ios/example/ios/SplitTests/SplitClientConfigHelperTests.swift index d07edd7..2c29a06 100644 --- a/splitio_ios/example/ios/SplitTests/SplitClientConfigHelperTests.swift +++ b/splitio_ios/example/ios/SplitTests/SplitClientConfigHelperTests.swift @@ -127,12 +127,31 @@ class SplitClientConfigHelperTests: XCTestCase { ] let splitClientConfig: SplitClientConfig = SplitClientConfigHelper.fromMap(configurationMap: configValues, impressionListener: nil) - let actualConfig = splitClientConfig.certificatePinningConfig?.pins + let actualConfig = splitClientConfig.certificatePinningConfig!.pins - let containsPins = actualConfig?.contains { pin in - (pin.host == "host1" && pin.algo == KeyHashAlgo.sha256) && - (pin.host == "host1" && pin.algo == KeyHashAlgo.sha1) && + let containsPins = actualConfig.contains { pin in + (pin.host == "host1" && pin.algo == KeyHashAlgo.sha256) } && + actualConfig.contains { pin in + (pin.host == "host1" && pin.algo == KeyHashAlgo.sha1) } && + actualConfig.contains { pin in (pin.host == "host2" && pin.algo == KeyHashAlgo.sha256 ) } + + XCTAssertTrue(containsPins) + } + + func testRolloutCacheConfigurationValuesAreMappedCorrectly() { + let configValues = [ + "rolloutCacheConfiguration": [ + "expirationDays": 5, + "clearOnInit": true + ] + ] + + let splitClientConfig: SplitClientConfig = SplitClientConfigHelper.fromMap(configurationMap: configValues, impressionListener: nil) + let actualConfig = splitClientConfig.rolloutCacheConfiguration! + + XCTAssertEqual(5, actualConfig.expirationDays) + XCTAssertTrue(actualConfig.clearOnInit) } } diff --git a/splitio_ios/example/ios/SplitTests/SplitTests.swift b/splitio_ios/example/ios/SplitTests/SplitTests.swift index 7f378bb..a749dde 100644 --- a/splitio_ios/example/ios/SplitTests/SplitTests.swift +++ b/splitio_ios/example/ios/SplitTests/SplitTests.swift @@ -356,6 +356,7 @@ class SplitClientStub: SplitClient { var clearAttributesCalled: Bool = false var sdkReadyEventAction: SplitAction? + // MARK: Evaluation feature func getTreatment(_ split: String, attributes: [String: Any]?) -> String { methodCalls["getTreatment"] = true return SplitConstants.control @@ -365,7 +366,7 @@ class SplitClientStub: SplitClient { methodCalls["getTreatment"] = true return SplitConstants.control } - + func getTreatments(splits: [String], attributes: [String: Any]?) -> [String: String] { methodCalls["getTreatments"] = true return ["feature": SplitConstants.control] @@ -385,45 +386,48 @@ class SplitClientStub: SplitClient { methodCalls["getTreatmentsWithConfig"] = true return ["feature": SplitResult(treatment: SplitConstants.control)] } - - func getTreatmentsByFlagSet(_ flagSet: String, attributes: [String : Any]?) -> [String : String] { - methodCalls["getTreatmentsByFlagSet"] = true - return [:] + + // MARK: Evaluation with Properties (EvaluationOptions) + func getTreatment(_ split: String, attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> String { + methodCalls["getTreatment"] = true + return SplitConstants.control } - - func getTreatmentsByFlagSets(_ flagSets: [String], attributes: [String : Any]?) -> [String : String] { - methodCalls["getTreatmentsByFlagSets"] = true - return [:] + + func getTreatments(splits: [String], attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: String] { + methodCalls["getTreatments"] = true + return ["feature": SplitConstants.control] } - - func getTreatmentsWithConfigByFlagSet(_ flagSet: String, attributes: [String : Any]?) -> [String : SplitResult] { - methodCalls["getTreatmentsWithConfigByFlagSet"] = true - return [:] + + func getTreatmentWithConfig(_ split: String, attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> SplitResult { + methodCalls["getTreatmentWithConfig"] = true + return SplitResult(treatment: SplitConstants.control) } - - func getTreatmentsWithConfigByFlagSets(_ flagSets: [String], attributes: [String : Any]?) -> [String : SplitResult] { - methodCalls["getTreatmentsWithConfigByFlagSets"] = true - return [:] + + func getTreatmentsWithConfig(splits: [String], attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: SplitResult] { + methodCalls["getTreatmentsWithConfig"] = true + return ["feature": SplitResult(treatment: SplitConstants.control)] } - func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction) { + // MARK: Event handling + func on(event: SplitEvent, execute action: @escaping SplitAction) { if event == .sdkReady { sdkReadyEventAction = action } } - - func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction) { + + func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction) { if event == .sdkReady { sdkReadyEventAction = action } } - - func on(event: SplitEvent, execute action: @escaping SplitAction) { + + func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction) { if event == .sdkReady { sdkReadyEventAction = action } } + // MARK: Track feature func track(trafficType: String, eventType: String) -> Bool { return true } @@ -470,6 +474,7 @@ class SplitClientStub: SplitClient { return true } + // MARK: Persistent attributes feature func setAttribute(name: String, value: Any) -> Bool { attributeNameValue = name attributeValue = value @@ -499,6 +504,7 @@ class SplitClientStub: SplitClient { return true } + // MARK: Client lifecycle func flush() { methodCalls["flush"] = true } @@ -510,6 +516,48 @@ class SplitClientStub: SplitClient { func destroy(completion: (() -> Void)?) { destroyCalled = true } + + // MARK: Evaluation with flagsets + func getTreatmentsByFlagSet(_ flagSet: String, attributes: [String: Any]?) -> [String: String] { + methodCalls["getTreatmentsByFlagSet"] = true + return [:] + } + + func getTreatmentsByFlagSets(_ flagSets: [String], attributes: [String: Any]?) -> [String: String] { + methodCalls["getTreatmentsByFlagSets"] = true + return [:] + } + + func getTreatmentsWithConfigByFlagSet(_ flagSet: String, attributes: [String: Any]?) -> [String: SplitResult] { + methodCalls["getTreatmentsWithConfigByFlagSet"] = true + return [:] + } + + func getTreatmentsWithConfigByFlagSets(_ flagSets: [String], attributes: [String: Any]?) -> [String: SplitResult] { + methodCalls["getTreatmentsWithConfigByFlagSets"] = true + return [:] + } + + // MARK: Evaluation with flagsets and properties (EvaluationOptions) + func getTreatmentsByFlagSet(_ flagSet: String, attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: String] { + methodCalls["getTreatmentsByFlagSet"] = true + return [:] + } + + func getTreatmentsByFlagSets(_ flagSets: [String], attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: String] { + methodCalls["getTreatmentsByFlagSets"] = true + return [:] + } + + func getTreatmentsWithConfigByFlagSet(_ flagSet: String, attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: SplitResult] { + methodCalls["getTreatmentsWithConfigByFlagSet"] = true + return [:] + } + + func getTreatmentsWithConfigByFlagSets(_ flagSets: [String], attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: SplitResult] { + methodCalls["getTreatmentsWithConfigByFlagSets"] = true + return [:] + } } class SplitManagerStub: SplitManager, Destroyable { diff --git a/splitio_ios/example/pubspec.lock b/splitio_ios/example/pubspec.lock index 23342f6..5d236bc 100644 --- a/splitio_ios/example/pubspec.lock +++ b/splitio_ios/example/pubspec.lock @@ -5,42 +5,42 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.13.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" cupertino_icons: dependency: "direct main" description: @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" flutter: dependency: "direct main" description: flutter @@ -79,18 +79,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -111,34 +111,34 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.16.0" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" plugin_platform_interface: dependency: transitive description: @@ -151,69 +151,69 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" splitio_ios: dependency: "direct main" description: path: ".." relative: true source: path - version: "0.1.9" + version: "1.0.0-rc.1" splitio_platform_interface: dependency: transitive description: path: "../../splitio_platform_interface" relative: true source: path - version: "1.4.0" + version: "2.0.0-rc.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.4" vector_math: dependency: transitive description: @@ -226,10 +226,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "15.0.0" sdks: - dart: ">=3.3.0 <4.0.0" + dart: ">=3.7.0-0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/splitio_ios/ios/Classes/Extensions.swift b/splitio_ios/ios/Classes/Extensions.swift index d17250c..1c8e65e 100644 --- a/splitio_ios/ios/Classes/Extensions.swift +++ b/splitio_ios/ios/Classes/Extensions.swift @@ -1,7 +1,7 @@ import Split extension Impression { - public func toMap() -> [String: Any?] { + func toMap() -> [String: Any?] { ["key": keyName, "bucketingKey": bucketingKey, "split": feature, @@ -9,7 +9,8 @@ extension Impression { "time": time, "appliedRule": label, "changeNumber": changeNumber, - "attributes": attributes] + "attributes": attributes, + "properties": properties] } } @@ -24,7 +25,9 @@ extension SplitView { "changeNumber": splitView.changeNumber, "configs": splitView.configs, "defaultTreatment": splitView.defaultTreatment, - "sets": splitView.sets + "sets": splitView.sets, + "impressionsDisabled": splitView.impressionsDisabled, + "prerequisites": splitView.prerequisites ] } else { return [:] diff --git a/splitio_ios/ios/Classes/SplitClientConfigHelper.swift b/splitio_ios/ios/Classes/SplitClientConfigHelper.swift index 025237b..abe76d0 100644 --- a/splitio_ios/ios/Classes/SplitClientConfigHelper.swift +++ b/splitio_ios/ios/Classes/SplitClientConfigHelper.swift @@ -33,6 +33,9 @@ class SplitClientConfigHelper { static private let READY_TIMEOUT = "readyTimeout" static private let CERTIFICATE_PINNING_CONFIGURATION = "certificatePinningConfiguration" static private let CERTIFICATE_PINNING_CONFIGURATION_PINS = "pins"; + static private let ROLLOUT_CACHE_CONFIGURATION = "rolloutCacheConfiguration" + static private let ROLLOUT_CACHE_CONFIGURATION_EXPIRATION = "expirationDays" + static private let ROLLOUT_CACHE_CONFIGURATION_CLEAR_ON_INIT = "clearOnInit" static func fromMap(configurationMap: [String: Any?], impressionListener: SplitImpressionListener?) -> SplitClientConfig { let config = SplitClientConfig() @@ -117,31 +120,31 @@ class SplitClientConfigHelper { if configurationMap[SDK_ENDPOINT] != nil { if let sdkEndpoint = configurationMap[SDK_ENDPOINT] as? String { - serviceEndpointsBuilder.set(sdkEndpoint: sdkEndpoint) + _ = serviceEndpointsBuilder.set(sdkEndpoint: sdkEndpoint) } } if configurationMap[EVENTS_ENDPOINT] != nil { if let eventsEndpoint = configurationMap[EVENTS_ENDPOINT] as? String { - serviceEndpointsBuilder.set(eventsEndpoint: eventsEndpoint) + _ = serviceEndpointsBuilder.set(eventsEndpoint: eventsEndpoint) } } if configurationMap[SSE_AUTH_SERVICE_ENDPOINT] != nil { if let sseAuthServiceEndpoint = configurationMap[SSE_AUTH_SERVICE_ENDPOINT] as? String { - serviceEndpointsBuilder.set(authServiceEndpoint: sseAuthServiceEndpoint) + _ = serviceEndpointsBuilder.set(authServiceEndpoint: sseAuthServiceEndpoint) } } if configurationMap[STREAMING_SERVICE_ENDPOINT] != nil { if let streamingServiceEndpoint = configurationMap[STREAMING_SERVICE_ENDPOINT] as? String { - serviceEndpointsBuilder.set(streamingServiceEndpoint: streamingServiceEndpoint) + _ = serviceEndpointsBuilder.set(streamingServiceEndpoint: streamingServiceEndpoint) } } if configurationMap[TELEMETRY_SERVICE_ENDPOINT] != nil { if let telemetryServiceEndpoint = configurationMap[TELEMETRY_SERVICE_ENDPOINT] as? String { - serviceEndpointsBuilder.set(telemetryServiceEndpoint: telemetryServiceEndpoint) + _ = serviceEndpointsBuilder.set(telemetryServiceEndpoint: telemetryServiceEndpoint) } } @@ -234,6 +237,22 @@ class SplitClientConfigHelper { } } + if let rolloutCacheConfig = configurationMap[ROLLOUT_CACHE_CONFIGURATION] as? [String: Any?] { + let rolloutCacheConfigurationBuilder = RolloutCacheConfiguration.builder() + if rolloutCacheConfig[ROLLOUT_CACHE_CONFIGURATION_EXPIRATION] != nil { + if let expirationDays = rolloutCacheConfig[ROLLOUT_CACHE_CONFIGURATION_EXPIRATION] as? Int { + rolloutCacheConfigurationBuilder.set(expirationDays: expirationDays) + } + } + + if rolloutCacheConfig[ROLLOUT_CACHE_CONFIGURATION_CLEAR_ON_INIT] != nil { + if let clearOnInit = rolloutCacheConfig[ROLLOUT_CACHE_CONFIGURATION_CLEAR_ON_INIT] as? Bool { + rolloutCacheConfigurationBuilder.set(clearOnInit: clearOnInit) + } + } + config.rolloutCacheConfiguration = rolloutCacheConfigurationBuilder.build() + } + return config } diff --git a/splitio_ios/ios/splitio_ios.podspec b/splitio_ios/ios/splitio_ios.podspec index a593c06..bb24308 100644 --- a/splitio_ios/ios/splitio_ios.podspec +++ b/splitio_ios/ios/splitio_ios.podspec @@ -4,7 +4,7 @@ # Pod::Spec.new do |s| s.name = 'splitio_ios' - s.version = '0.7.0' + s.version = '0.8.0' s.summary = 'split.io official Flutter plugin.' s.description = <<-DESC split.io official Flutter plugin. @@ -15,7 +15,7 @@ split.io official Flutter plugin. s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'Split', '~> 3.0.0' + s.dependency 'Split', '~> 3.3.2' s.platform = :ios, '9.0' # Flutter.framework does not contain a i386 slice. diff --git a/splitio_ios/pubspec.yaml b/splitio_ios/pubspec.yaml index 5e77494..ae0269e 100644 --- a/splitio_ios/pubspec.yaml +++ b/splitio_ios/pubspec.yaml @@ -1,7 +1,7 @@ name: splitio_ios description: The official iOS implementation of splitio Flutter plugin. repository: https://github.com/splitio/flutter-sdk-plugin/tree/main/splitio_ios -version: 0.2.0 +version: 1.0.0-rc.1 environment: sdk: ">=2.16.2 <4.0.0" @@ -18,7 +18,8 @@ flutter: dependencies: flutter: sdk: flutter - splitio_platform_interface: ^1.5.0 + splitio_platform_interface: # ^2.0.0-rc.1 + path: ../splitio_platform_interface dev_dependencies: flutter_test: diff --git a/splitio_ios/test/splitio_ios_test.dart b/splitio_ios/test/splitio_ios_test.dart index ab04f62..df312aa 100644 --- a/splitio_ios/test/splitio_ios_test.dart +++ b/splitio_ios/test/splitio_ios_test.dart @@ -190,7 +190,6 @@ void main() { }); }); - test('getTreatmentsByFlagSet without attributes', () async { _platform.getTreatmentsByFlagSet( matchingKey: 'matching-key', @@ -503,14 +502,20 @@ void main() { apiKey: 'api-key', matchingKey: 'matching-key', bucketingKey: 'bucketing-key', - sdkConfiguration: - SplitConfiguration(logLevel: SplitLogLevel.error, streamingEnabled: false, readyTimeout: 1)); + sdkConfiguration: SplitConfiguration( + logLevel: SplitLogLevel.error, + streamingEnabled: false, + readyTimeout: 1)); expect(methodName, 'init'); expect(methodArguments, { 'apiKey': 'api-key', 'matchingKey': 'matching-key', 'bucketingKey': 'bucketing-key', - 'sdkConfiguration': {'logLevel': 'error', 'streamingEnabled': false, 'readyTimeout': 1}, + 'sdkConfiguration': { + 'logLevel': 'error', + 'streamingEnabled': false, + 'readyTimeout': 1 + }, }); }); }); @@ -632,6 +637,7 @@ void main() { expect(impression.appliedRule, 'appliedRule'); expect(impression.changeNumber, 200); expect(impression.attributes, {}); + expect(impression.properties, {}); }), ); _simulateMethodInvocation('impressionLog', key: 'matching-key', arguments: { @@ -642,11 +648,11 @@ void main() { 'time': 3000, 'appliedRule': 'appliedRule', 'changeNumber': 200, - 'attributes': {} + 'attributes': {}, + 'properties': {} }); }); - group('userConsent', () { test('get user consent', () async { UserConsent userConsent = await _platform.getUserConsent(); diff --git a/splitio_platform_interface/LICENSE b/splitio_platform_interface/LICENSE index 68e2d99..af74bff 100644 --- a/splitio_platform_interface/LICENSE +++ b/splitio_platform_interface/LICENSE @@ -176,7 +176,7 @@ END OF TERMS AND CONDITIONS - Copyright © 2024 Split Software, Inc. + Copyright © 2025 Split Software, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/splitio_platform_interface/lib/method_channel_platform.dart b/splitio_platform_interface/lib/method_channel_platform.dart index d3fb11d..aaf0b2c 100644 --- a/splitio_platform_interface/lib/method_channel_platform.dart +++ b/splitio_platform_interface/lib/method_channel_platform.dart @@ -22,6 +22,19 @@ class MethodChannelPlatform extends SplitioPlatform { } } + Map _withEvalOptions( + String matchingKey, + String? bucketingKey, { + required Map base, + required EvaluationOptions evaluationOptions, + }) { + final args = Map.from(base); + if (evaluationOptions.properties.isNotEmpty) { + args['evaluationOptions'] = evaluationOptions.toJson(); + } + return _buildParameters(matchingKey, bucketingKey, args); + } + @override Future init( {required String apiKey, @@ -107,11 +120,19 @@ class MethodChannelPlatform extends SplitioPlatform { {required String matchingKey, required String? bucketingKey, required String splitName, - Map attributes = const {}}) async { - return await methodChannel.invokeMethod( - 'getTreatment', - _buildParameters(matchingKey, bucketingKey, - {'splitName': splitName, 'attributes': attributes})) ?? + Map attributes = const {}, + EvaluationOptions evaluationOptions = + const EvaluationOptions.empty()}) async { + final params = _withEvalOptions( + matchingKey, + bucketingKey, + base: { + 'splitName': splitName, + 'attributes': attributes, + }, + evaluationOptions: evaluationOptions, + ); + return await methodChannel.invokeMethod('getTreatment', params) ?? _controlTreatment; } @@ -120,14 +141,23 @@ class MethodChannelPlatform extends SplitioPlatform { {required String matchingKey, required String? bucketingKey, required String splitName, - Map attributes = const {}}) async { - Map? treatment = (await methodChannel.invokeMapMethod( - 'getTreatmentWithConfig', - _buildParameters(matchingKey, bucketingKey, - {'splitName': splitName, 'attributes': attributes}))) - ?.entries - .first - .value; + Map attributes = const {}, + EvaluationOptions evaluationOptions = + const EvaluationOptions.empty()}) async { + final params = _withEvalOptions( + matchingKey, + bucketingKey, + base: { + 'splitName': splitName, + 'attributes': attributes, + }, + evaluationOptions: evaluationOptions, + ); + Map? treatment = + (await methodChannel.invokeMapMethod('getTreatmentWithConfig', params)) + ?.entries + .first + .value; if (treatment == null) { return _controlResult; } @@ -140,11 +170,20 @@ class MethodChannelPlatform extends SplitioPlatform { {required String matchingKey, required String? bucketingKey, required List splitNames, - Map attributes = const {}}) async { - Map? treatments = await methodChannel.invokeMapMethod( - 'getTreatments', - _buildParameters(matchingKey, bucketingKey, - {'splitName': splitNames, 'attributes': attributes})); + Map attributes = const {}, + EvaluationOptions evaluationOptions = + const EvaluationOptions.empty()}) async { + final params = _withEvalOptions( + matchingKey, + bucketingKey, + base: { + 'splitName': splitNames, + 'attributes': attributes, + }, + evaluationOptions: evaluationOptions, + ); + Map? treatments = + await methodChannel.invokeMapMethod('getTreatments', params); return treatments ?.map((key, value) => MapEntry(key, value)) ?? @@ -156,11 +195,20 @@ class MethodChannelPlatform extends SplitioPlatform { {required String matchingKey, required String? bucketingKey, required List splitNames, - Map attributes = const {}}) async { - Map? treatments = await methodChannel.invokeMapMethod( - 'getTreatmentsWithConfig', - _buildParameters(matchingKey, bucketingKey, - {'splitName': splitNames, 'attributes': attributes})); + Map attributes = const {}, + EvaluationOptions evaluationOptions = + const EvaluationOptions.empty()}) async { + final params = _withEvalOptions( + matchingKey, + bucketingKey, + base: { + 'splitName': splitNames, + 'attributes': attributes, + }, + evaluationOptions: evaluationOptions, + ); + Map? treatments = + await methodChannel.invokeMapMethod('getTreatmentsWithConfig', params); return treatments?.map((key, value) => MapEntry(key, SplitResult(value['treatment'], value['config']))) ?? @@ -172,11 +220,20 @@ class MethodChannelPlatform extends SplitioPlatform { {required String matchingKey, required String? bucketingKey, required String flagSet, - Map attributes = const {}}) async { - Map? treatments = await methodChannel.invokeMapMethod( - 'getTreatmentsByFlagSet', - _buildParameters(matchingKey, bucketingKey, - {'flagSet': flagSet, 'attributes': attributes})); + Map attributes = const {}, + EvaluationOptions evaluationOptions = + const EvaluationOptions.empty()}) async { + final params = _withEvalOptions( + matchingKey, + bucketingKey, + base: { + 'flagSet': flagSet, + 'attributes': attributes, + }, + evaluationOptions: evaluationOptions, + ); + Map? treatments = + await methodChannel.invokeMapMethod('getTreatmentsByFlagSet', params); return treatments ?.map((key, value) => MapEntry(key, value)) ?? @@ -188,11 +245,20 @@ class MethodChannelPlatform extends SplitioPlatform { {required String matchingKey, required String? bucketingKey, required List flagSets, - Map attributes = const {}}) async { - Map? treatments = await methodChannel.invokeMapMethod( - 'getTreatmentsByFlagSets', - _buildParameters(matchingKey, bucketingKey, - {'flagSets': flagSets, 'attributes': attributes})); + Map attributes = const {}, + EvaluationOptions evaluationOptions = + const EvaluationOptions.empty()}) async { + final params = _withEvalOptions( + matchingKey, + bucketingKey, + base: { + 'flagSets': flagSets, + 'attributes': attributes, + }, + evaluationOptions: evaluationOptions, + ); + Map? treatments = + await methodChannel.invokeMapMethod('getTreatmentsByFlagSets', params); return treatments ?.map((key, value) => MapEntry(key, value)) ?? @@ -204,11 +270,20 @@ class MethodChannelPlatform extends SplitioPlatform { {required String matchingKey, required String? bucketingKey, required String flagSet, - Map attributes = const {}}) async { + Map attributes = const {}, + EvaluationOptions evaluationOptions = + const EvaluationOptions.empty()}) async { + final params = _withEvalOptions( + matchingKey, + bucketingKey, + base: { + 'flagSet': flagSet, + 'attributes': attributes, + }, + evaluationOptions: evaluationOptions, + ); Map? treatments = await methodChannel.invokeMapMethod( - 'getTreatmentsWithConfigByFlagSet', - _buildParameters(matchingKey, bucketingKey, - {'flagSet': flagSet, 'attributes': attributes})); + 'getTreatmentsWithConfigByFlagSet', params); return treatments?.map((key, value) => MapEntry(key, SplitResult(value['treatment'], value['config']))) ?? @@ -220,14 +295,23 @@ class MethodChannelPlatform extends SplitioPlatform { {required String matchingKey, required String? bucketingKey, required List flagSets, - Map attributes = const {}}) async { + Map attributes = const {}, + EvaluationOptions evaluationOptions = + const EvaluationOptions.empty()}) async { + final params = _withEvalOptions( + matchingKey, + bucketingKey, + base: { + 'flagSets': flagSets, + 'attributes': attributes, + }, + evaluationOptions: evaluationOptions, + ); Map? treatments = await methodChannel.invokeMapMethod( - 'getTreatmentsWithConfigByFlagSets', - _buildParameters(matchingKey, bucketingKey, - {'flagSets': flagSets, 'attributes': attributes})); + 'getTreatmentsWithConfigByFlagSets', params); return treatments?.map((key, value) => - MapEntry(key, SplitResult(value['treatment'], value['config']))) ?? + MapEntry(key, SplitResult(value['treatment'], value['config']))) ?? {}; } diff --git a/splitio_platform_interface/lib/split_configuration.dart b/splitio_platform_interface/lib/split_configuration.dart index e6a12fc..ffe9a9d 100644 --- a/splitio_platform_interface/lib/split_configuration.dart +++ b/splitio_platform_interface/lib/split_configuration.dart @@ -1,5 +1,6 @@ import 'package:splitio_platform_interface/split_certificate_pinning_configuration.dart'; import 'package:splitio_platform_interface/split_sync_config.dart'; +import 'package:splitio_platform_interface/split_rollout_cache_configuration.dart'; class SplitConfiguration { final Map configurationMap = {}; @@ -74,6 +75,7 @@ class SplitConfiguration { SplitLogLevel? logLevel, int? readyTimeout = 10, CertificatePinningConfiguration? certificatePinningConfiguration, + RolloutCacheConfiguration? rolloutCacheConfiguration, }) { if (featuresRefreshRate != null) { configurationMap['featuresRefreshRate'] = featuresRefreshRate; @@ -186,6 +188,13 @@ class SplitConfiguration { 'pins': certificatePinningConfiguration.pins }; } + + if (rolloutCacheConfiguration != null) { + configurationMap['rolloutCacheConfiguration'] = { + 'expirationDays': rolloutCacheConfiguration.expirationDays, + 'clearOnInit': rolloutCacheConfiguration.clearOnInit + }; + } } } diff --git a/splitio_platform_interface/lib/split_evaluation_options.dart b/splitio_platform_interface/lib/split_evaluation_options.dart new file mode 100644 index 0000000..2a603a8 --- /dev/null +++ b/splitio_platform_interface/lib/split_evaluation_options.dart @@ -0,0 +1,20 @@ +import 'dart:collection'; + +class EvaluationOptions { + final Map _properties; + + Map get properties => UnmodifiableMapView(_properties); + + const EvaluationOptions.empty() : _properties = const {}; + + factory EvaluationOptions([Map properties = const {}]) { + return EvaluationOptions._( + Map.unmodifiable(Map.from(properties))); + } + + Map toJson() => { + 'properties': _properties, + }; + + const EvaluationOptions._(this._properties); +} diff --git a/splitio_platform_interface/lib/split_impression.dart b/splitio_platform_interface/lib/split_impression.dart index 6deec0b..3bfdf4e 100644 --- a/splitio_platform_interface/lib/split_impression.dart +++ b/splitio_platform_interface/lib/split_impression.dart @@ -7,11 +7,18 @@ class Impression { final String? appliedRule; final num? changeNumber; final Map attributes; + final Map? properties; Impression(this.key, this.bucketingKey, this.split, this.treatment, this.time, - this.appliedRule, this.changeNumber, this.attributes); + this.appliedRule, this.changeNumber, this.attributes, this.properties); static Impression fromMap(Map map) { + var properties = null; + if (map['properties'] != null) { + properties = Map.from(map['properties'] as Map); + } else { + properties = null; + } return Impression( map['key'] as String?, map['bucketingKey'] as String?, @@ -20,11 +27,12 @@ class Impression { map['time'] as num?, map['appliedRule'] as String?, map['changeNumber'] as num?, - Map.from(map['attributes'] as Map)); + Map.from(map['attributes'] as Map), + properties); } @override String toString() { - return 'Impression = {"key":$key, "bucketingKey":$bucketingKey, "split":$split, "treatment":$treatment, "time":$time, "appliedRule": $appliedRule, "changeNumber":$changeNumber, "attributes":$attributes}'; + return 'Impression = {"key":$key, "bucketingKey":$bucketingKey, "split":$split, "treatment":$treatment, "time":$time, "appliedRule": $appliedRule, "changeNumber":$changeNumber, "attributes":$attributes, "properties":$properties}'; } } diff --git a/splitio_platform_interface/lib/split_prerequisite.dart b/splitio_platform_interface/lib/split_prerequisite.dart new file mode 100644 index 0000000..b8be081 --- /dev/null +++ b/splitio_platform_interface/lib/split_prerequisite.dart @@ -0,0 +1,49 @@ +class Prerequisite { + final String _name; + + final Set _treatments; + + String get name => _name; + + Set get treatments => _treatments; + + Prerequisite(this._name, this._treatments); + + static Prerequisite fromEntry(el) { + final String name = (el['n'] ?? el['n:'] ?? '').toString(); + final List rawTreatments = (el['t'] as List?) ?? []; + final Set treatments = + rawTreatments.map((e) => e.toString()).toSet(); + + return Prerequisite(name, treatments); + } + + @override + String toString() { + return '''Prerequisite = { + name: $name, + treatments: $treatments + }'''; + } + + equals(Prerequisite other) { + return name == other.name && treatments == other.treatments; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is Prerequisite && + name == other.name && + other.treatments.containsAll(treatments); + } + + @override + int get hashCode { + int treatmentsHash = 0; + for (final t in _treatments) { + treatmentsHash ^= t.hashCode; + } + return name.hashCode ^ treatmentsHash; + } +} diff --git a/splitio_platform_interface/lib/split_rollout_cache_configuration.dart b/splitio_platform_interface/lib/split_rollout_cache_configuration.dart new file mode 100644 index 0000000..9c2b0f3 --- /dev/null +++ b/splitio_platform_interface/lib/split_rollout_cache_configuration.dart @@ -0,0 +1,16 @@ +class RolloutCacheConfiguration { + + late int _expirationDays; + late bool _clearOnInit; + + int get expirationDays => _expirationDays; + bool get clearOnInit => _clearOnInit; + + RolloutCacheConfiguration({int expirationDays = 10, bool clearOnInit = false}) { + if (expirationDays < 1) { + expirationDays = 10; + } + _expirationDays = expirationDays; + _clearOnInit = clearOnInit; + } +} diff --git a/splitio_platform_interface/lib/split_view.dart b/splitio_platform_interface/lib/split_view.dart index 789daa1..9cc2916 100644 --- a/splitio_platform_interface/lib/split_view.dart +++ b/splitio_platform_interface/lib/split_view.dart @@ -1,60 +1,92 @@ import 'dart:core'; +import 'package:splitio_platform_interface/split_prerequisite.dart'; + class SplitView { + static const String _keyName = 'name'; + static const String _keyTrafficType = 'trafficType'; + static const String _keyKilled = 'killed'; + static const String _keyTreatments = 'treatments'; + static const String _keyChangeNumber = 'changeNumber'; + static const String _keyConfigs = 'configs'; + static const String _keyDefaultTreatment = 'defaultTreatment'; + static const String _keySets = 'sets'; + static const String _keyImpressionsDisabled = 'impressionsDisabled'; + static const String _keyPrerequisites = 'prerequisites'; + String name; String trafficType; bool killed = false; List treatments = []; - int changeNumber; + int? changeNumber; Map configs = {}; String defaultTreatment; List sets = []; + bool impressionsDisabled = false; + Set prerequisites = {}; SplitView(this.name, this.trafficType, this.killed, this.treatments, this.changeNumber, this.configs, - [this.defaultTreatment = '', this.sets = const []]); + [this.defaultTreatment = '', + this.sets = const [], + this.impressionsDisabled = false, + this.prerequisites = const {}]); - static SplitView? fromEntry(Map? entry) { + static SplitView? fromEntry(Map? entry) { if (entry == null || entry.isEmpty) { return null; } final Map mappedConfig = {}; - entry['configs']?.entries.forEach((MapEntry entry) => { + entry[_keyConfigs]?.entries.forEach((MapEntry entry) => { mappedConfig.addAll({entry.key.toString(): entry.value.toString()}) }); - if (entry['treatments'] == null) { - entry['treatments'] = entry['treatments'] ?? []; + if (entry[_keyTreatments] == null) { + entry[_keyTreatments] = entry[_keyTreatments] ?? []; + } + + if (entry[_keySets] == null) { + entry[_keySets] = []; } - if (entry['sets'] == null) { - entry['sets'] = []; + if (entry[_keyImpressionsDisabled] == null) { + entry[_keyImpressionsDisabled] = false; } + if (entry[_keyPrerequisites] == null) { + entry[_keyPrerequisites] = []; + } + + final List prereqRaw = (entry[_keyPrerequisites] as List?) ?? []; + final Set prerequisites = + prereqRaw.map((el) => Prerequisite.fromEntry(el)).toSet(); + return SplitView( - entry['name'], - entry['trafficType'], - entry['killed'], - (entry['treatments'] as List).map((el) => el as String).toList(), - entry['changeNumber'], + entry[_keyName], + entry[_keyTrafficType], + entry[_keyKilled], + (entry[_keyTreatments] as List).map((el) => el as String).toList(), + entry[_keyChangeNumber], mappedConfig, - entry['defaultTreatment'] ?? '', - (entry['sets'] as List).map((el) => el as String).toList() - ); + entry[_keyDefaultTreatment] ?? '', + (entry[_keySets] as List).map((el) => el as String).toList(), + entry[_keyImpressionsDisabled] ?? false, + prerequisites); } @override String toString() { return '''SplitView = { - name: $name, - trafficType: $trafficType, - killed: $killed, - treatments: ${treatments.toString()}, - changeNumber: $changeNumber, - config: $configs, - defaultTreatment: $defaultTreatment, - sets: ${sets.toString()} + $_keyName: $name, + $_keyTrafficType: $trafficType, + $_keyKilled: $killed, + $_keyTreatments: ${treatments.toString()}, + $_keyChangeNumber: $changeNumber, + $_keyConfigs: $configs, + $_keyDefaultTreatment: $defaultTreatment, + $_keySets: ${sets.toString()}, + $_keyImpressionsDisabled: $impressionsDisabled }'''; } } diff --git a/splitio_platform_interface/lib/splitio_platform_interface.dart b/splitio_platform_interface/lib/splitio_platform_interface.dart index a367fdf..247c3a2 100644 --- a/splitio_platform_interface/lib/splitio_platform_interface.dart +++ b/splitio_platform_interface/lib/splitio_platform_interface.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:splitio_platform_interface/method_channel_platform.dart'; import 'package:splitio_platform_interface/split_configuration.dart'; +import 'package:splitio_platform_interface/split_evaluation_options.dart'; import 'package:splitio_platform_interface/split_impression.dart'; import 'package:splitio_platform_interface/split_result.dart'; import 'package:splitio_platform_interface/split_view.dart'; @@ -12,7 +13,9 @@ export 'package:splitio_platform_interface/impressions/impressions_method_call_h export 'package:splitio_platform_interface/method_call_handler.dart'; export 'package:splitio_platform_interface/method_channel_platform.dart'; export 'package:splitio_platform_interface/split_configuration.dart'; +export 'package:splitio_platform_interface/split_evaluation_options.dart'; export 'package:splitio_platform_interface/split_impression.dart'; +export 'package:splitio_platform_interface/split_prerequisite.dart'; export 'package:splitio_platform_interface/split_result.dart'; export 'package:splitio_platform_interface/split_view.dart'; export 'package:splitio_platform_interface/splitio_platform_interface.dart'; @@ -62,7 +65,8 @@ abstract class _ClientPlatform { {required String matchingKey, required String? bucketingKey, required String splitName, - Map attributes = const {}}) { + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) { throw UnimplementedError(); } @@ -70,7 +74,8 @@ abstract class _ClientPlatform { {required String matchingKey, required String? bucketingKey, required String splitName, - Map attributes = const {}}) { + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) { throw UnimplementedError(); } @@ -78,7 +83,8 @@ abstract class _ClientPlatform { {required String matchingKey, required String? bucketingKey, required List splitNames, - Map attributes = const {}}) { + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) { throw UnimplementedError(); } @@ -86,7 +92,8 @@ abstract class _ClientPlatform { {required String matchingKey, required String? bucketingKey, required List splitNames, - Map attributes = const {}}) { + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) { throw UnimplementedError(); } @@ -94,7 +101,8 @@ abstract class _ClientPlatform { {required String matchingKey, required String? bucketingKey, required String flagSet, - Map attributes = const {}}) { + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) { throw UnimplementedError(); } @@ -102,7 +110,8 @@ abstract class _ClientPlatform { {required String matchingKey, required String? bucketingKey, required List flagSets, - Map attributes = const {}}) { + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) { throw UnimplementedError(); } @@ -110,7 +119,8 @@ abstract class _ClientPlatform { {required String matchingKey, required String? bucketingKey, required String flagSet, - Map attributes = const {}}) { + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) { throw UnimplementedError(); } @@ -118,7 +128,8 @@ abstract class _ClientPlatform { {required String matchingKey, required String? bucketingKey, required List flagSets, - Map attributes = const {}}) { + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) { throw UnimplementedError(); } diff --git a/splitio_platform_interface/pubspec.yaml b/splitio_platform_interface/pubspec.yaml index 72db712..3465a95 100644 --- a/splitio_platform_interface/pubspec.yaml +++ b/splitio_platform_interface/pubspec.yaml @@ -2,7 +2,7 @@ name: splitio_platform_interface description: A common platform interface for the splitio plugin. # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 1.5.0 +version: 2.0.0-rc.1 repository: https://github.com/splitio/flutter-sdk-plugin/tree/main/splitio_platform_interface environment: diff --git a/splitio_platform_interface/test/method_channel_platform_test.dart b/splitio_platform_interface/test/method_channel_platform_test.dart index 5dceddf..3692397 100644 --- a/splitio_platform_interface/test/method_channel_platform_test.dart +++ b/splitio_platform_interface/test/method_channel_platform_test.dart @@ -2,6 +2,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:splitio_platform_interface/method_channel_platform.dart'; import 'package:splitio_platform_interface/split_configuration.dart'; +import 'package:splitio_platform_interface/split_evaluation_options.dart'; void main() { const MethodChannel _channel = MethodChannel('splitio'); @@ -65,6 +66,184 @@ void main() { }); }); + group('evaluationOptions serialization', () { + test('getTreatment includes evaluationOptions when non-empty', () async { + final eo = EvaluationOptions({'x': 1, 'y': 'z'}); + await _platform.getTreatment( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + splitName: 'split', + evaluationOptions: eo, + ); + + expect(methodName, 'getTreatment'); + expect(methodArguments, { + 'splitName': 'split', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {}, + 'evaluationOptions': { + 'properties': {'x': 1, 'y': 'z'} + }, + }); + }); + + test('getTreatmentWithConfig includes evaluationOptions when non-empty', + () async { + final eo = EvaluationOptions({'x': 1, 'y': 'z'}); + await _platform.getTreatmentWithConfig( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + splitName: 'split1', + evaluationOptions: eo, + ); + + expect(methodName, 'getTreatmentWithConfig'); + expect(methodArguments, { + 'splitName': 'split1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {}, + 'evaluationOptions': { + 'properties': {'x': 1, 'y': 'z'} + }, + }); + }); + + test('getTreatments includes evaluationOptions when non-empty', () async { + final eo = EvaluationOptions({'x': 1, 'y': 'z'}); + await _platform.getTreatments( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + splitNames: ['split1', 'split2'], + evaluationOptions: eo, + ); + + expect(methodName, 'getTreatments'); + expect(methodArguments, { + 'splitName': ['split1', 'split2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {}, + 'evaluationOptions': { + 'properties': {'x': 1, 'y': 'z'} + }, + }); + }); + + test('getTreatmentsWithConfig includes evaluationOptions when non-empty', + () async { + final eo = EvaluationOptions({'x': 1, 'y': 'z'}); + await _platform.getTreatmentsWithConfig( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + splitNames: ['split1', 'split2'], + evaluationOptions: eo, + ); + + expect(methodName, 'getTreatmentsWithConfig'); + expect(methodArguments, { + 'splitName': ['split1', 'split2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {}, + 'evaluationOptions': { + 'properties': {'x': 1, 'y': 'z'} + }, + }); + }); + + test('getTreatmentsByFlagSet includes evaluationOptions when non-empty', + () async { + final eo = EvaluationOptions({'x': 1, 'y': 'z'}); + await _platform.getTreatmentsByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1', + evaluationOptions: eo, + ); + + expect(methodName, 'getTreatmentsByFlagSet'); + expect(methodArguments, { + 'flagSet': 'set_1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {}, + 'evaluationOptions': { + 'properties': {'x': 1, 'y': 'z'} + }, + }); + }); + + test('getTreatmentsByFlagSets includes evaluationOptions when non-empty', + () async { + final eo = EvaluationOptions({'x': 1, 'y': 'z'}); + await _platform.getTreatmentsByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2'], + evaluationOptions: eo, + ); + + expect(methodName, 'getTreatmentsByFlagSets'); + expect(methodArguments, { + 'flagSets': ['set_1', 'set_2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {}, + 'evaluationOptions': { + 'properties': {'x': 1, 'y': 'z'} + }, + }); + }); + + test( + 'getTreatmentsWithConfigByFlagSet includes evaluationOptions when non-empty', + () async { + final eo = EvaluationOptions({'x': 1, 'y': 'z'}); + await _platform.getTreatmentsWithConfigByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1', + evaluationOptions: eo, + ); + + expect(methodName, 'getTreatmentsWithConfigByFlagSet'); + expect(methodArguments, { + 'flagSet': 'set_1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {}, + 'evaluationOptions': { + 'properties': {'x': 1, 'y': 'z'} + }, + }); + }); + + test( + 'getTreatmentsWithConfigByFlagSets includes evaluationOptions when non-empty', + () async { + final eo = EvaluationOptions({'x': 1, 'y': 'z'}); + await _platform.getTreatmentsWithConfigByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2'], + evaluationOptions: eo, + ); + + expect(methodName, 'getTreatmentsWithConfigByFlagSets'); + expect(methodArguments, { + 'flagSets': ['set_1', 'set_2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {}, + 'evaluationOptions': { + 'properties': {'x': 1, 'y': 'z'} + }, + }); + }); + }); + group('evaluation', () { test('getTreatment without attributes', () async { _platform.getTreatment( @@ -509,7 +688,11 @@ void main() { 'apiKey': 'api-key', 'matchingKey': 'matching-key', 'bucketingKey': 'bucketing-key', - 'sdkConfiguration': {'logLevel': 'debug', 'streamingEnabled': false, 'readyTimeout' : 10}, + 'sdkConfiguration': { + 'logLevel': 'debug', + 'streamingEnabled': false, + 'readyTimeout': 10 + }, }); }); }); @@ -631,6 +814,7 @@ void main() { expect(impression.appliedRule, 'appliedRule'); expect(impression.changeNumber, 200); expect(impression.attributes, {}); + expect(impression.properties, {'prop1': 'value1', 'prop2': 'value2'}); }), ); _simulateMethodInvocation('impressionLog', key: 'matching-key', arguments: { @@ -641,7 +825,8 @@ void main() { 'time': 3000, 'appliedRule': 'appliedRule', 'changeNumber': 200, - 'attributes': {} + 'attributes': {}, + 'properties': {'prop1': 'value1', 'prop2': 'value2'} }); }); diff --git a/splitio_platform_interface/test/prerequisites_test.dart b/splitio_platform_interface/test/prerequisites_test.dart new file mode 100644 index 0000000..c3c0909 --- /dev/null +++ b/splitio_platform_interface/test/prerequisites_test.dart @@ -0,0 +1,32 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:splitio_platform_interface/split_prerequisite.dart'; + +void main() { + group('Prerequisite', () { + test('fromEntry creates correct instance', () { + final entry = { + 'n': 'feature1', + 't': ['on', 'off'] + }; + final prereq = Prerequisite.fromEntry(entry); + expect(prereq.name, 'feature1'); + expect(prereq.treatments, {'on', 'off'}); + }); + + test('equality and hashCode', () { + final a = Prerequisite('feat', {'a', 'b'}); + final b = Prerequisite('feat', {'b', 'a'}); + final c = Prerequisite('feat2', {'a', 'b'}); + expect(a, equals(b)); + expect(a.hashCode, equals(b.hashCode)); + expect(a, isNot(equals(c))); + }); + + test('toString contains name and treatments', () { + final prereq = Prerequisite('myFeature', {'t1'}); + final str = prereq.toString(); + expect(str, contains('myFeature')); + expect(str, contains('t1')); + }); + }); +} diff --git a/splitio_platform_interface/test/rollout_cache_configuration_test.dart b/splitio_platform_interface/test/rollout_cache_configuration_test.dart new file mode 100644 index 0000000..c7c3306 --- /dev/null +++ b/splitio_platform_interface/test/rollout_cache_configuration_test.dart @@ -0,0 +1,23 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:splitio_platform_interface/split_rollout_cache_configuration.dart'; + +void main() { + test('negative expirationDays defaults to 10', () { + var config = RolloutCacheConfiguration(expirationDays: -1); + + expect(config.expirationDays, 10); + }); + + test('zero expirationDays defaults to 10', () { + var config = RolloutCacheConfiguration(expirationDays: 0); + + expect(config.expirationDays, 10); + }); + + test('default values', () { + var config = RolloutCacheConfiguration(); + + expect(config.expirationDays, 10); + expect(config.clearOnInit, false); + }); +} \ No newline at end of file diff --git a/splitio_platform_interface/test/split_evaluation_options_test.dart b/splitio_platform_interface/test/split_evaluation_options_test.dart new file mode 100644 index 0000000..a473c33 --- /dev/null +++ b/splitio_platform_interface/test/split_evaluation_options_test.dart @@ -0,0 +1,51 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:splitio_platform_interface/split_evaluation_options.dart'; + +void main() { + group('EvaluationOptions', () { + test('default constructor has empty properties and is unmodifiable', () { + final eo = EvaluationOptions(); + expect(eo.properties, isEmpty); + expect(() => eo.properties['x'] = 1, throwsA(isA())); + expect(() => eo.properties.remove('x'), throwsA(isA())); + expect(() => eo.properties.clear(), throwsA(isA())); + }); + + test('constructor with properties stores them', () { + final props = {'a': 1, 'b': true, 'c': 'x'}; + final eo = EvaluationOptions(props); + expect(eo.properties, equals(props)); + }); + + test('internal map is decoupled from external map', () { + final props = {'k': 42}; + final eo = EvaluationOptions(props); + // Not the same instance + expect(identical(eo.properties, props), isFalse); + // Mutating the original does not change eo.properties + props['k'] = 99; + expect(eo.properties['k'], 42); + }); + + test('properties getter is unmodifiable', () { + final eo = EvaluationOptions({'k': 1}); + expect(() => eo.properties['k'] = 2, throwsA(isA())); + expect(() => eo.properties.addAll({'z': 0}), + throwsA(isA())); + }); + + test('toJson returns a map with properties key', () { + final props = {'x': 1, 'y': 'z'}; + final eo = EvaluationOptions(props); + final json = eo.toJson(); + expect(json.keys, contains('properties')); + expect(json['properties'], equals(props)); + }); + + test('toJson returns empty properties for empty options', () { + const eo = EvaluationOptions.empty(); + final json = eo.toJson(); + expect(json, equals({'properties': const {}})); + }); + }); +} diff --git a/splitio_platform_interface/test/split_impression_test.dart b/splitio_platform_interface/test/split_impression_test.dart index b993d2a..689599d 100644 --- a/splitio_platform_interface/test/split_impression_test.dart +++ b/splitio_platform_interface/test/split_impression_test.dart @@ -12,6 +12,7 @@ void main() { 'appliedRule': 'appliedRule', 'changeNumber': 12512512, 'attributes': {'good': true}, + 'properties': {'bad': false, 'number': 10.5}, }; Impression expectedImpression = Impression( @@ -23,6 +24,7 @@ void main() { 'appliedRule', 12512512, {'good': true}, + {'bad': false, 'number': 10.5}, ); Impression impression = Impression.fromMap(sourceMap); @@ -34,5 +36,6 @@ void main() { expect(impression.appliedRule, expectedImpression.appliedRule); expect(impression.changeNumber, expectedImpression.changeNumber); expect(impression.attributes, expectedImpression.attributes); + expect(impression.properties, expectedImpression.properties); }); } diff --git a/splitio_platform_interface/test/split_view_test.dart b/splitio_platform_interface/test/split_view_test.dart index 9a5aee1..f3040b5 100644 --- a/splitio_platform_interface/test/split_view_test.dart +++ b/splitio_platform_interface/test/split_view_test.dart @@ -1,4 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; +import 'package:splitio_platform_interface/split_prerequisite.dart'; import 'package:splitio_platform_interface/split_view.dart'; void main() { @@ -24,6 +25,17 @@ void main() { 'trafficType': 'default', 'defaultTreatment': 'on', 'sets': ['set1', 'set2'], + 'impressionsDisabled': true, + 'prerequisites': [ + { + 'n:': 'pre1', + 't': ['on', 'off'] + }, + { + 'n': 'pre2', + 't': ['off'] + } + ], }); expect(splitView?.name, 'my_split'); @@ -34,5 +46,10 @@ void main() { expect(splitView?.trafficType, 'default'); expect(splitView?.defaultTreatment, 'on'); expect(splitView?.sets, ['set1', 'set2']); + expect(splitView?.impressionsDisabled, true); + expect(splitView?.prerequisites, { + Prerequisite('pre1', {'on', 'off'}), + Prerequisite('pre2', {'off'}) + }); }); } diff --git a/splitio_platform_interface/test/splitio_configuration_test.dart b/splitio_platform_interface/test/splitio_configuration_test.dart index a98d9ce..64b758c 100644 --- a/splitio_platform_interface/test/splitio_configuration_test.dart +++ b/splitio_platform_interface/test/splitio_configuration_test.dart @@ -1,6 +1,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:splitio_platform_interface/split_certificate_pinning_configuration.dart'; import 'package:splitio_platform_interface/split_configuration.dart'; +import 'package:splitio_platform_interface/split_rollout_cache_configuration.dart'; import 'package:splitio_platform_interface/split_sync_config.dart'; void main() { @@ -34,7 +35,8 @@ void main() { certificatePinningConfiguration: CertificatePinningConfiguration() .addPin('host1', 'pin1') .addPin('host2', 'pin3') - .addPin('host1', 'pin2')); + .addPin('host1', 'pin2'), + rolloutCacheConfiguration: RolloutCacheConfiguration(expirationDays: 15, clearOnInit: true)); expect(config.configurationMap['eventFlushInterval'], 2000); expect(config.configurationMap['eventsPerPush'], 300); @@ -71,6 +73,8 @@ void main() { 'host1': ['pin1', 'pin2'], 'host2': ['pin3'] }); + expect(config.configurationMap['rolloutCacheConfiguration']['expirationDays'], 15); + expect(config.configurationMap['rolloutCacheConfiguration']['clearOnInit'], true); }); test('no special values leaves map empty', () async {