From 111d0e4bec9f999bba6ed0ad2b933645ebf860d3 Mon Sep 17 00:00:00 2001 From: gthea Date: Tue, 28 Jan 2025 10:28:00 -0300 Subject: [PATCH 01/31] RolloutCacheConfiguration & SplitView updates in platform_interface (#143) --- .../lib/split_configuration.dart | 9 +++ .../split_rollout_cache_configuration.dart | 16 +++++ .../lib/split_view.dart | 60 ++++++++++++------- .../rollout_cache_configuration_test.dart | 23 +++++++ .../test/split_view_test.dart | 2 + .../test/splitio_configuration_test.dart | 6 +- 6 files changed, 94 insertions(+), 22 deletions(-) create mode 100644 splitio_platform_interface/lib/split_rollout_cache_configuration.dart create mode 100644 splitio_platform_interface/test/rollout_cache_configuration_test.dart 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_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..f8e0a30 100644 --- a/splitio_platform_interface/lib/split_view.dart +++ b/splitio_platform_interface/lib/split_view.dart @@ -1,6 +1,17 @@ import 'dart:core'; 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'; + String name; String trafficType; bool killed = false; @@ -9,10 +20,11 @@ class SplitView { Map configs = {}; String defaultTreatment; List sets = []; + bool impressionsDisabled = false; 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]); static SplitView? fromEntry(Map? entry) { if (entry == null || entry.isEmpty) { @@ -20,41 +32,47 @@ class SplitView { } 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; } 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 ); } @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/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_view_test.dart b/splitio_platform_interface/test/split_view_test.dart index 9a5aee1..a45f6eb 100644 --- a/splitio_platform_interface/test/split_view_test.dart +++ b/splitio_platform_interface/test/split_view_test.dart @@ -24,6 +24,7 @@ void main() { 'trafficType': 'default', 'defaultTreatment': 'on', 'sets': ['set1', 'set2'], + 'impressionsDisabled': true, }); expect(splitView?.name, 'my_split'); @@ -34,5 +35,6 @@ void main() { expect(splitView?.trafficType, 'default'); expect(splitView?.defaultTreatment, 'on'); expect(splitView?.sets, ['set1', 'set2']); + expect(splitView?.impressionsDisabled, true); }); } 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 { From f184762b7562d506a64a2d1088e26c119abc85c7 Mon Sep 17 00:00:00 2001 From: gthea Date: Tue, 28 Jan 2025 16:23:32 -0300 Subject: [PATCH 02/31] Update config & method parser in Android implementation (#144) --- splitio/example/pubspec.lock | 7 +++---- splitio/pubspec.yaml | 4 +++- splitio_android/android/build.gradle | 2 +- .../splitio/SplitClientConfigHelper.java | 20 +++++++++++++++++++ .../split/splitio/SplitMethodParserImpl.java | 1 + .../splitio/SplitClientConfigHelperTest.java | 16 +++++++++++++++ 6 files changed, 44 insertions(+), 6 deletions(-) diff --git a/splitio/example/pubspec.lock b/splitio/example/pubspec.lock index 8da00b5..00b0f01 100644 --- a/splitio/example/pubspec.lock +++ b/splitio/example/pubspec.lock @@ -170,10 +170,9 @@ packages: splitio_android: dependency: transitive description: - name: splitio_android - sha256: "44b0e1dddd374fc73fc1b5ef89598b96ea405d533a8211c06a45665f5d6187b5" - url: "https://pub.dev" - source: hosted + path: "../../splitio_android" + relative: true + source: path version: "0.2.0" splitio_ios: dependency: transitive diff --git a/splitio/pubspec.yaml b/splitio/pubspec.yaml index 3e6d086..f15dc10 100644 --- a/splitio/pubspec.yaml +++ b/splitio/pubspec.yaml @@ -1,3 +1,4 @@ +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 @@ -19,7 +20,8 @@ flutter: dependencies: flutter: sdk: flutter - splitio_android: ^0.2.0 + splitio_android: + path: ../splitio_android splitio_ios: ^0.2.0 splitio_platform_interface: ^1.5.0 diff --git a/splitio_android/android/build.gradle b/splitio_android/android/build.gradle index f943883..e808137 100644 --- a/splitio_android/android/build.gradle +++ b/splitio_android/android/build.gradle @@ -36,7 +36,7 @@ android { } dependencies { - implementation 'io.split.client:android-client:5.0.0' + implementation 'io.split.client:android-client:5.1.0-rc1' 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/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..7631b13 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 @@ -474,6 +474,7 @@ private static Map getSplitViewAsMap(@Nullable SplitView splitVi splitViewMap.put("configs", splitView.configs); splitViewMap.put("defaultTreatment", splitView.defaultTreatment); splitViewMap.put("sets", splitView.sets); + splitViewMap.put("impressionsDisabled", splitView.impressionsDisabled); return splitViewMap; } 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()); + } } From b781991f2df2cbaff33ac24f1c480b5f9eb6c778 Mon Sep 17 00:00:00 2001 From: gthea Date: Wed, 29 Jan 2025 10:59:33 -0300 Subject: [PATCH 03/31] Update config & method parser in iOS implementation (#145) --- splitio/example/ios/Podfile | 2 ++ splitio/example/ios/Podfile.lock | 23 +++++++++------ splitio/example/pubspec.lock | 14 ++++----- splitio/pubspec.yaml | 9 +++--- splitio_android/pubspec.yaml | 3 +- splitio_ios/example/ios/Podfile | 4 +++ splitio_ios/example/ios/Podfile.lock | 23 +++++++++------ .../ios/SplitTests/ExtensionsTests.swift | 27 +++++++++++++++++ .../SplitClientConfigHelperTests.swift | 27 ++++++++++++++--- splitio_ios/example/pubspec.lock | 4 +-- splitio_ios/ios/Classes/Extensions.swift | 5 ++-- .../ios/Classes/SplitClientConfigHelper.swift | 29 +++++++++++++++---- splitio_ios/ios/splitio_ios.podspec | 2 +- splitio_ios/pubspec.yaml | 3 +- 14 files changed, 129 insertions(+), 46 deletions(-) diff --git a/splitio/example/ios/Podfile b/splitio/example/ios/Podfile index 2c068c4..0b6609e 100644 --- a/splitio/example/ios/Podfile +++ b/splitio/example/ios/Podfile @@ -32,6 +32,8 @@ target 'Runner' do use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + + pod 'Split', :git => 'https://github.com/splitio/ios-client.git', :branch => 'SDKS-9073_baseline' # TODO: remove; development only end post_install do |installer| diff --git a/splitio/example/ios/Podfile.lock b/splitio/example/ios/Podfile.lock index ddaefcf..56de32d 100644 --- a/splitio/example/ios/Podfile.lock +++ b/splitio/example/ios/Podfile.lock @@ -1,29 +1,34 @@ PODS: - Flutter (1.0.0) - - Split (3.0.0) + - Split (3.1.0) - splitio_ios (0.7.0): - Flutter - - Split (~> 3.0.0) + - Split DEPENDENCIES: - Flutter (from `Flutter`) + - Split (from `https://github.com/splitio/ios-client.git`, branch `SDKS-9073_baseline`) - splitio_ios (from `.symlinks/plugins/splitio_ios/ios`) -SPEC REPOS: - trunk: - - Split - EXTERNAL SOURCES: Flutter: :path: Flutter + Split: + :branch: SDKS-9073_baseline + :git: https://github.com/splitio/ios-client.git splitio_ios: :path: ".symlinks/plugins/splitio_ios/ios" +CHECKOUT OPTIONS: + Split: + :commit: 708427ff99d24e2f2ae6e5a672ee23efebafba06 + :git: https://github.com/splitio/ios-client.git + SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - Split: 66424040ad573d052f58269f841e71b34578a916 - splitio_ios: e4e3becbe89cae0a2fa9ca03a575c21f23af0d90 + Split: 17f15abcc74b39c3a8d670f59e787163626ad6b5 + splitio_ios: 00bf48283a9e3f9497a973d9b5cafc5d414dd427 -PODFILE CHECKSUM: 4e8f8b2be68aeea4c0d5beb6ff1e79fface1d048 +PODFILE CHECKSUM: a52d9df387b5aca8aed91ec989839c51add619b2 COCOAPODS: 1.15.0 diff --git a/splitio/example/pubspec.lock b/splitio/example/pubspec.lock index 00b0f01..a3269a9 100644 --- a/splitio/example/pubspec.lock +++ b/splitio/example/pubspec.lock @@ -177,18 +177,16 @@ packages: splitio_ios: dependency: transitive description: - name: splitio_ios - sha256: "7c7a2a60711b8e6267cde7e2754d30931dafc76b20b28e1356624963628cb166" - url: "https://pub.dev" - source: hosted + path: "../../splitio_ios" + relative: true + source: path version: "0.2.0" splitio_platform_interface: dependency: transitive description: - name: splitio_platform_interface - sha256: "2f0457991d18d654486264a66dacf54c7cf23cd88bbb73ed299d69dbbc2fd49b" - url: "https://pub.dev" - source: hosted + path: "../../splitio_platform_interface" + relative: true + source: path version: "1.5.0" stack_trace: dependency: transitive diff --git a/splitio/pubspec.yaml b/splitio/pubspec.yaml index f15dc10..73c522c 100644 --- a/splitio/pubspec.yaml +++ b/splitio/pubspec.yaml @@ -20,11 +20,12 @@ flutter: dependencies: flutter: sdk: flutter - splitio_android: + splitio_android: # ^0.2.0 path: ../splitio_android - splitio_ios: ^0.2.0 - splitio_platform_interface: ^1.5.0 - + splitio_ios: # ^0.2.0 + path: ../splitio_ios + splitio_platform_interface: # ^1.5.0 + path: ../splitio_platform_interface dev_dependencies: flutter_test: sdk: flutter diff --git a/splitio_android/pubspec.yaml b/splitio_android/pubspec.yaml index cc75d6e..79bcfb8 100644 --- a/splitio_android/pubspec.yaml +++ b/splitio_android/pubspec.yaml @@ -19,7 +19,8 @@ flutter: dependencies: flutter: sdk: flutter - splitio_platform_interface: ^1.5.0 + splitio_platform_interface: # ^1.5.0 + path: ../splitio_platform_interface dev_dependencies: flutter_test: diff --git a/splitio_ios/example/ios/Podfile b/splitio_ios/example/ios/Podfile index 8629561..c4289ff 100644 --- a/splitio_ios/example/ios/Podfile +++ b/splitio_ios/example/ios/Podfile @@ -32,6 +32,8 @@ target 'Runner' do use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + + pod 'Split', :git => 'https://github.com/splitio/ios-client.git', :branch => 'SDKS-9073_baseline' # TODO: remove; development only end target 'SplitTests' do @@ -39,6 +41,8 @@ target 'SplitTests' do use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + + pod 'Split', :git => 'https://github.com/splitio/ios-client.git', :branch => 'SDKS-9073_baseline' # TODO: remove; development only end post_install do |installer| diff --git a/splitio_ios/example/ios/Podfile.lock b/splitio_ios/example/ios/Podfile.lock index f1c80a0..5ee5b26 100644 --- a/splitio_ios/example/ios/Podfile.lock +++ b/splitio_ios/example/ios/Podfile.lock @@ -1,29 +1,34 @@ PODS: - Flutter (1.0.0) - - Split (3.0.0) + - Split (3.1.0) - splitio_ios (0.7.0): - Flutter - - Split (~> 3.0.0) + - Split DEPENDENCIES: - Flutter (from `Flutter`) + - Split (from `https://github.com/splitio/ios-client.git`, branch `SDKS-9073_baseline`) - splitio_ios (from `.symlinks/plugins/splitio_ios/ios`) -SPEC REPOS: - trunk: - - Split - EXTERNAL SOURCES: Flutter: :path: Flutter + Split: + :branch: SDKS-9073_baseline + :git: https://github.com/splitio/ios-client.git splitio_ios: :path: ".symlinks/plugins/splitio_ios/ios" +CHECKOUT OPTIONS: + Split: + :commit: 708427ff99d24e2f2ae6e5a672ee23efebafba06 + :git: https://github.com/splitio/ios-client.git + SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - Split: 66424040ad573d052f58269f841e71b34578a916 - splitio_ios: e4e3becbe89cae0a2fa9ca03a575c21f23af0d90 + Split: 17f15abcc74b39c3a8d670f59e787163626ad6b5 + splitio_ios: 00bf48283a9e3f9497a973d9b5cafc5d414dd427 -PODFILE CHECKSUM: aed42fc5c94ade572556b7ed357c5c57f1bd83a2 +PODFILE CHECKSUM: 3e7633332e3580ada5ac333ff9c0b05e8c0b7972 COCOAPODS: 1.15.0 diff --git a/splitio_ios/example/ios/SplitTests/ExtensionsTests.swift b/splitio_ios/example/ios/SplitTests/ExtensionsTests.swift index 8cc0e67..cdb123e 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 { @@ -26,4 +27,30 @@ class ExtensionsTests: XCTestCase { "split": "my-split", "time": 16161616])) } + + 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"] + splitView.impressionsDisabled = true + + let splitViewMap = SplitView.asMap(splitView: splitView) + XCTAssert(splitViewMap.count == 9) + 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])) + } } 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/pubspec.lock b/splitio_ios/example/pubspec.lock index 23342f6..ec19606 100644 --- a/splitio_ios/example/pubspec.lock +++ b/splitio_ios/example/pubspec.lock @@ -166,14 +166,14 @@ packages: path: ".." relative: true source: path - version: "0.1.9" + version: "0.2.0" splitio_platform_interface: dependency: transitive description: path: "../../splitio_platform_interface" relative: true source: path - version: "1.4.0" + version: "1.5.0" stack_trace: dependency: transitive description: diff --git a/splitio_ios/ios/Classes/Extensions.swift b/splitio_ios/ios/Classes/Extensions.swift index d17250c..fdad567 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, @@ -24,7 +24,8 @@ extension SplitView { "changeNumber": splitView.changeNumber, "configs": splitView.configs, "defaultTreatment": splitView.defaultTreatment, - "sets": splitView.sets + "sets": splitView.sets, + "impressionsDisabled": splitView.impressionsDisabled, ] } 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..78c16a3 100644 --- a/splitio_ios/ios/splitio_ios.podspec +++ b/splitio_ios/ios/splitio_ios.podspec @@ -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' # TODO: specify version 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..06849c8 100644 --- a/splitio_ios/pubspec.yaml +++ b/splitio_ios/pubspec.yaml @@ -18,7 +18,8 @@ flutter: dependencies: flutter: sdk: flutter - splitio_platform_interface: ^1.5.0 + splitio_platform_interface: # ^1.5.0 + path: ../splitio_platform_interface dev_dependencies: flutter_test: From 241752be8b540181f393d14464ef254d48907f05 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:40:14 -0300 Subject: [PATCH 04/31] Updated License Year (#142) --- splitio/LICENSE | 2 +- splitio_android/LICENSE | 2 +- splitio_ios/LICENSE | 2 +- splitio_platform_interface/LICENSE | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) 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_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_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_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. From c51e189c214bb06f14aaf5c4d51c3496d68c0a84 Mon Sep 17 00:00:00 2001 From: gthea Date: Mon, 11 Aug 2025 17:47:36 -0300 Subject: [PATCH 05/31] Add prereqs to SplitView (#148) --- splitio/example/android/app/build.gradle | 29 ++--------- splitio/example/android/build.gradle | 11 ----- splitio/example/android/settings.gradle | 29 ++++++++--- splitio_android/android/build.gradle | 12 +++-- .../split/splitio/ImpressionListenerImp.java | 2 +- .../splitio/ImpressionListenerImpTest.java | 5 +- .../lib/split_prerequisite.dart | 49 +++++++++++++++++++ .../lib/split_view.dart | 22 +++++++-- splitio_platform_interface/pubspec.yaml | 2 +- .../test/prerequisites_test.dart | 32 ++++++++++++ .../test/split_view_test.dart | 15 ++++++ 11 files changed, 150 insertions(+), 58 deletions(-) create mode 100644 splitio_platform_interface/lib/split_prerequisite.dart create mode 100644 splitio_platform_interface/test/prerequisites_test.dart diff --git a/splitio/example/android/app/build.gradle b/splitio/example/android/app/build.gradle index e231f55..947a9f1 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,6 @@ android { applicationId "io.split.splitio_example" minSdkVersion flutter.minSdkVersion targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName } 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_android/android/build.gradle b/splitio_android/android/build.gradle index e808137..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.1.0-rc1' + 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/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/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_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_view.dart b/splitio_platform_interface/lib/split_view.dart index f8e0a30..e5f9fc9 100644 --- a/splitio_platform_interface/lib/split_view.dart +++ b/splitio_platform_interface/lib/split_view.dart @@ -1,7 +1,8 @@ import 'dart:core'; -class SplitView { +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'; @@ -11,6 +12,7 @@ class SplitView { static const String _keyDefaultTreatment = 'defaultTreatment'; static const String _keySets = 'sets'; static const String _keyImpressionsDisabled = 'impressionsDisabled'; + static const String _keyPrerequisites = 'prerequisites'; String name; String trafficType; @@ -21,10 +23,14 @@ class SplitView { 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.impressionsDisabled = false]); + [this.defaultTreatment = '', + this.sets = const [], + this.impressionsDisabled = false, + this.prerequisites = const {}]); static SplitView? fromEntry(Map? entry) { if (entry == null || entry.isEmpty) { @@ -48,6 +54,14 @@ class SplitView { 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[_keyName], entry[_keyTrafficType], @@ -57,8 +71,8 @@ class SplitView { mappedConfig, entry[_keyDefaultTreatment] ?? '', (entry[_keySets] as List).map((el) => el as String).toList(), - entry[_keyImpressionsDisabled] ?? false - ); + entry[_keyImpressionsDisabled] ?? false, + prerequisites); } @override diff --git a/splitio_platform_interface/pubspec.yaml b/splitio_platform_interface/pubspec.yaml index 72db712..0200526 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: 1.6.0-rc.1 repository: https://github.com/splitio/flutter-sdk-plugin/tree/main/splitio_platform_interface environment: 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/split_view_test.dart b/splitio_platform_interface/test/split_view_test.dart index a45f6eb..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() { @@ -25,6 +26,16 @@ void main() { 'defaultTreatment': 'on', 'sets': ['set1', 'set2'], 'impressionsDisabled': true, + 'prerequisites': [ + { + 'n:': 'pre1', + 't': ['on', 'off'] + }, + { + 'n': 'pre2', + 't': ['off'] + } + ], }); expect(splitView?.name, 'my_split'); @@ -36,5 +47,9 @@ void main() { expect(splitView?.defaultTreatment, 'on'); expect(splitView?.sets, ['set1', 'set2']); expect(splitView?.impressionsDisabled, true); + expect(splitView?.prerequisites, { + Prerequisite('pre1', {'on', 'off'}), + Prerequisite('pre2', {'off'}) + }); }); } From 62dfc287559042b90322c6370984119eaf1d0b68 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 12 Aug 2025 17:31:28 -0300 Subject: [PATCH 06/31] WIP --- .../lib/method_channel_platform.dart | 106 +++++++--- .../lib/split_evaluation_options.dart | 20 ++ .../lib/splitio_platform_interface.dart | 27 ++- splitio_platform_interface/pubspec.yaml | 2 +- .../test/method_channel_platform_test.dart | 185 +++++++++++++++++- .../test/split_evaluation_options_test.dart | 52 +++++ 6 files changed, 356 insertions(+), 36 deletions(-) create mode 100644 splitio_platform_interface/lib/split_evaluation_options.dart create mode 100644 splitio_platform_interface/test/split_evaluation_options_test.dart diff --git a/splitio_platform_interface/lib/method_channel_platform.dart b/splitio_platform_interface/lib/method_channel_platform.dart index d3fb11d..e92969c 100644 --- a/splitio_platform_interface/lib/method_channel_platform.dart +++ b/splitio_platform_interface/lib/method_channel_platform.dart @@ -107,11 +107,17 @@ class MethodChannelPlatform extends SplitioPlatform { {required String matchingKey, required String? bucketingKey, required String splitName, - Map attributes = const {}}) async { + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) async { + final args = { + 'splitName': splitName, + 'attributes': attributes, + }; + if (evaluationOptions.properties.isNotEmpty) { + args['evaluationOptions'] = evaluationOptions.toJson(); + } return await methodChannel.invokeMethod( - 'getTreatment', - _buildParameters(matchingKey, bucketingKey, - {'splitName': splitName, 'attributes': attributes})) ?? + 'getTreatment', _buildParameters(matchingKey, bucketingKey, args)) ?? _controlTreatment; } @@ -120,11 +126,18 @@ class MethodChannelPlatform extends SplitioPlatform { {required String matchingKey, required String? bucketingKey, required String splitName, - Map attributes = const {}}) async { + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) async { + final args = { + 'splitName': splitName, + 'attributes': attributes, + }; + if (evaluationOptions.properties.isNotEmpty) { + args['evaluationOptions'] = evaluationOptions.toJson(); + } Map? treatment = (await methodChannel.invokeMapMethod( 'getTreatmentWithConfig', - _buildParameters(matchingKey, bucketingKey, - {'splitName': splitName, 'attributes': attributes}))) + _buildParameters(matchingKey, bucketingKey, args))) ?.entries .first .value; @@ -140,11 +153,17 @@ class MethodChannelPlatform extends SplitioPlatform { {required String matchingKey, required String? bucketingKey, required List splitNames, - Map attributes = const {}}) async { + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) async { + final args = { + 'splitName': splitNames, + 'attributes': attributes, + }; + if (evaluationOptions.properties.isNotEmpty) { + args['evaluationOptions'] = evaluationOptions.toJson(); + } Map? treatments = await methodChannel.invokeMapMethod( - 'getTreatments', - _buildParameters(matchingKey, bucketingKey, - {'splitName': splitNames, 'attributes': attributes})); + 'getTreatments', _buildParameters(matchingKey, bucketingKey, args)); return treatments ?.map((key, value) => MapEntry(key, value)) ?? @@ -156,11 +175,18 @@ class MethodChannelPlatform extends SplitioPlatform { {required String matchingKey, required String? bucketingKey, required List splitNames, - Map attributes = const {}}) async { + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) async { + final args = { + 'splitName': splitNames, + 'attributes': attributes, + }; + if (evaluationOptions.properties.isNotEmpty) { + args['evaluationOptions'] = evaluationOptions.toJson(); + } Map? treatments = await methodChannel.invokeMapMethod( 'getTreatmentsWithConfig', - _buildParameters(matchingKey, bucketingKey, - {'splitName': splitNames, 'attributes': attributes})); + _buildParameters(matchingKey, bucketingKey, args)); return treatments?.map((key, value) => MapEntry(key, SplitResult(value['treatment'], value['config']))) ?? @@ -172,11 +198,18 @@ 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 args = { + 'flagSet': flagSet, + 'attributes': attributes, + }; + if (evaluationOptions.properties.isNotEmpty) { + args['evaluationOptions'] = evaluationOptions.toJson(); + } Map? treatments = await methodChannel.invokeMapMethod( 'getTreatmentsByFlagSet', - _buildParameters(matchingKey, bucketingKey, - {'flagSet': flagSet, 'attributes': attributes})); + _buildParameters(matchingKey, bucketingKey, args)); return treatments ?.map((key, value) => MapEntry(key, value)) ?? @@ -188,11 +221,18 @@ 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 args = { + 'flagSets': flagSets, + 'attributes': attributes, + }; + if (evaluationOptions.properties.isNotEmpty) { + args['evaluationOptions'] = evaluationOptions.toJson(); + } Map? treatments = await methodChannel.invokeMapMethod( 'getTreatmentsByFlagSets', - _buildParameters(matchingKey, bucketingKey, - {'flagSets': flagSets, 'attributes': attributes})); + _buildParameters(matchingKey, bucketingKey, args)); return treatments ?.map((key, value) => MapEntry(key, value)) ?? @@ -204,11 +244,18 @@ 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 args = { + 'flagSet': flagSet, + 'attributes': attributes, + }; + if (evaluationOptions.properties.isNotEmpty) { + args['evaluationOptions'] = evaluationOptions.toJson(); + } Map? treatments = await methodChannel.invokeMapMethod( 'getTreatmentsWithConfigByFlagSet', - _buildParameters(matchingKey, bucketingKey, - {'flagSet': flagSet, 'attributes': attributes})); + _buildParameters(matchingKey, bucketingKey, args)); return treatments?.map((key, value) => MapEntry(key, SplitResult(value['treatment'], value['config']))) ?? @@ -220,11 +267,18 @@ 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 args = { + 'flagSets': flagSets, + 'attributes': attributes, + }; + if (evaluationOptions.properties.isNotEmpty) { + args['evaluationOptions'] = evaluationOptions.toJson(); + } Map? treatments = await methodChannel.invokeMapMethod( 'getTreatmentsWithConfigByFlagSets', - _buildParameters(matchingKey, bucketingKey, - {'flagSets': flagSets, 'attributes': attributes})); + _buildParameters(matchingKey, bucketingKey, args)); return treatments?.map((key, value) => MapEntry(key, SplitResult(value['treatment'], value['config']))) ?? 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/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 0200526..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.6.0-rc.1 +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..5c4b57c 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 + }, }); }); }); 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..0490751 --- /dev/null +++ b/splitio_platform_interface/test/split_evaluation_options_test.dart @@ -0,0 +1,52 @@ +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 (defensive copy)', () { + 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 (cannot mutate through accessor)', + () { + 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 {}})); + }); + }); +} From ff2a069e79c21b38a30051011515e821dfba51c1 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 12 Aug 2025 17:37:40 -0300 Subject: [PATCH 07/31] Adding evaluation options methods --- .../lib/method_channel_platform.dart | 206 ++++++++++-------- 1 file changed, 118 insertions(+), 88 deletions(-) diff --git a/splitio_platform_interface/lib/method_channel_platform.dart b/splitio_platform_interface/lib/method_channel_platform.dart index e92969c..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, @@ -108,16 +121,18 @@ class MethodChannelPlatform extends SplitioPlatform { required String? bucketingKey, required String splitName, Map attributes = const {}, - EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) async { - final args = { - 'splitName': splitName, - 'attributes': attributes, - }; - if (evaluationOptions.properties.isNotEmpty) { - args['evaluationOptions'] = evaluationOptions.toJson(); - } - return await methodChannel.invokeMethod( - 'getTreatment', _buildParameters(matchingKey, bucketingKey, args)) ?? + 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; } @@ -127,20 +142,22 @@ class MethodChannelPlatform extends SplitioPlatform { required String? bucketingKey, required String splitName, Map attributes = const {}, - EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) async { - final args = { - 'splitName': splitName, - 'attributes': attributes, - }; - if (evaluationOptions.properties.isNotEmpty) { - args['evaluationOptions'] = evaluationOptions.toJson(); - } - Map? treatment = (await methodChannel.invokeMapMethod( - 'getTreatmentWithConfig', - _buildParameters(matchingKey, bucketingKey, args))) - ?.entries - .first - .value; + 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; } @@ -154,16 +171,19 @@ class MethodChannelPlatform extends SplitioPlatform { required String? bucketingKey, required List splitNames, Map attributes = const {}, - EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) async { - final args = { - 'splitName': splitNames, - 'attributes': attributes, - }; - if (evaluationOptions.properties.isNotEmpty) { - args['evaluationOptions'] = evaluationOptions.toJson(); - } - Map? treatments = await methodChannel.invokeMapMethod( - 'getTreatments', _buildParameters(matchingKey, bucketingKey, args)); + 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)) ?? @@ -176,17 +196,19 @@ class MethodChannelPlatform extends SplitioPlatform { required String? bucketingKey, required List splitNames, Map attributes = const {}, - EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) async { - final args = { - 'splitName': splitNames, - 'attributes': attributes, - }; - if (evaluationOptions.properties.isNotEmpty) { - args['evaluationOptions'] = evaluationOptions.toJson(); - } - Map? treatments = await methodChannel.invokeMapMethod( - 'getTreatmentsWithConfig', - _buildParameters(matchingKey, bucketingKey, args)); + 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']))) ?? @@ -199,17 +221,19 @@ class MethodChannelPlatform extends SplitioPlatform { required String? bucketingKey, required String flagSet, Map attributes = const {}, - EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) async { - final args = { - 'flagSet': flagSet, - 'attributes': attributes, - }; - if (evaluationOptions.properties.isNotEmpty) { - args['evaluationOptions'] = evaluationOptions.toJson(); - } - Map? treatments = await methodChannel.invokeMapMethod( - 'getTreatmentsByFlagSet', - _buildParameters(matchingKey, bucketingKey, args)); + 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)) ?? @@ -222,17 +246,19 @@ class MethodChannelPlatform extends SplitioPlatform { required String? bucketingKey, required List flagSets, Map attributes = const {}, - EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) async { - final args = { - 'flagSets': flagSets, - 'attributes': attributes, - }; - if (evaluationOptions.properties.isNotEmpty) { - args['evaluationOptions'] = evaluationOptions.toJson(); - } - Map? treatments = await methodChannel.invokeMapMethod( - 'getTreatmentsByFlagSets', - _buildParameters(matchingKey, bucketingKey, args)); + 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)) ?? @@ -245,17 +271,19 @@ class MethodChannelPlatform extends SplitioPlatform { required String? bucketingKey, required String flagSet, Map attributes = const {}, - EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) async { - final args = { - 'flagSet': flagSet, - 'attributes': attributes, - }; - if (evaluationOptions.properties.isNotEmpty) { - args['evaluationOptions'] = evaluationOptions.toJson(); - } + 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, args)); + 'getTreatmentsWithConfigByFlagSet', params); return treatments?.map((key, value) => MapEntry(key, SplitResult(value['treatment'], value['config']))) ?? @@ -268,20 +296,22 @@ class MethodChannelPlatform extends SplitioPlatform { required String? bucketingKey, required List flagSets, Map attributes = const {}, - EvaluationOptions evaluationOptions = const EvaluationOptions.empty()}) async { - final args = { - 'flagSets': flagSets, - 'attributes': attributes, - }; - if (evaluationOptions.properties.isNotEmpty) { - args['evaluationOptions'] = evaluationOptions.toJson(); - } + 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, args)); + 'getTreatmentsWithConfigByFlagSets', params); return treatments?.map((key, value) => - MapEntry(key, SplitResult(value['treatment'], value['config']))) ?? + MapEntry(key, SplitResult(value['treatment'], value['config']))) ?? {}; } From 091d5e894e7eb8ccbe694210639e19d94bb6927e Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 12 Aug 2025 17:40:21 -0300 Subject: [PATCH 08/31] Change test names' --- .../test/split_evaluation_options_test.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/splitio_platform_interface/test/split_evaluation_options_test.dart b/splitio_platform_interface/test/split_evaluation_options_test.dart index 0490751..a473c33 100644 --- a/splitio_platform_interface/test/split_evaluation_options_test.dart +++ b/splitio_platform_interface/test/split_evaluation_options_test.dart @@ -17,7 +17,7 @@ void main() { expect(eo.properties, equals(props)); }); - test('internal map is decoupled from external map (defensive copy)', () { + test('internal map is decoupled from external map', () { final props = {'k': 42}; final eo = EvaluationOptions(props); // Not the same instance @@ -27,8 +27,7 @@ void main() { expect(eo.properties['k'], 42); }); - test('properties getter is unmodifiable (cannot mutate through accessor)', - () { + test('properties getter is unmodifiable', () { final eo = EvaluationOptions({'k': 1}); expect(() => eo.properties['k'] = 2, throwsA(isA())); expect(() => eo.properties.addAll({'z': 0}), From bae5f5df4b26b55039290150661445b1767e3cea Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 12 Aug 2025 18:03:38 -0300 Subject: [PATCH 09/31] splitio updates --- splitio/example/pubspec.lock | 86 ++++++------- splitio/lib/split_client.dart | 88 +++++++++---- splitio/pubspec.yaml | 4 +- splitio/test/splitio_client_test.dart | 158 +++++++++++++++++++++++- splitio/test/splitio_platform_stub.dart | 57 +++++++-- 5 files changed, 313 insertions(+), 80 deletions(-) diff --git a/splitio/example/pubspec.lock b/splitio/example/pubspec.lock index a3269a9..8c2bff7 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,29 +151,29 @@ 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: path: "../../splitio_android" relative: true source: path - version: "0.2.0" + version: "0.3.0-rc.1" splitio_ios: dependency: transitive description: @@ -187,47 +187,47 @@ packages: path: "../../splitio_platform_interface" relative: true source: path - version: "1.5.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: @@ -240,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/pubspec.yaml b/splitio/pubspec.yaml index 73c522c..b771d85 100644 --- a/splitio/pubspec.yaml +++ b/splitio/pubspec.yaml @@ -1,7 +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/ @@ -24,7 +24,7 @@ dependencies: path: ../splitio_android splitio_ios: # ^0.2.0 path: ../splitio_ios - 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/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({}); } From b520a67a3dc17092fcafb17808d89451b58801dd Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 13 Aug 2025 11:29:17 -0300 Subject: [PATCH 10/31] Add properties to impresion in impression listener --- splitio_platform_interface/lib/split_impression.dart | 8 +++++--- .../test/split_impression_test.dart | 3 +++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/splitio_platform_interface/lib/split_impression.dart b/splitio_platform_interface/lib/split_impression.dart index 6deec0b..5a5b712 100644 --- a/splitio_platform_interface/lib/split_impression.dart +++ b/splitio_platform_interface/lib/split_impression.dart @@ -7,9 +7,10 @@ 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) { return Impression( @@ -20,11 +21,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), + Map.from(map['properties'] as Map)); } @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/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); }); } From a77fd238feec94fa2f3b92fb4dfda96d6ef754e6 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 13 Aug 2025 14:20:08 -0300 Subject: [PATCH 11/31] Prereqs, imp disabled & eval options in Android impl --- .../main/java/io/split/splitio/Constants.java | 1 + .../io/split/splitio/EvaluationWrapper.java | 17 +- .../split/splitio/SplitMethodParserImpl.java | 90 ++++++--- .../io/split/splitio/SplitWrapperImpl.java | 33 ++-- .../splitio/SplitMethodParserImplTest.java | 178 ++++++++++++++---- .../split/splitio/SplitWrapperImplTest.java | 41 ++-- splitio_android/pubspec.yaml | 4 +- .../test/method_channel_platform_test.dart | 4 +- 8 files changed, 262 insertions(+), 106 deletions(-) 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/SplitMethodParserImpl.java b/splitio_android/android/src/main/java/io/split/splitio/SplitMethodParserImpl.java index 7631b13..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,8 +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/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 79bcfb8..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,7 @@ 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: diff --git a/splitio_platform_interface/test/method_channel_platform_test.dart b/splitio_platform_interface/test/method_channel_platform_test.dart index 5c4b57c..3692397 100644 --- a/splitio_platform_interface/test/method_channel_platform_test.dart +++ b/splitio_platform_interface/test/method_channel_platform_test.dart @@ -814,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: { @@ -824,7 +825,8 @@ void main() { 'time': 3000, 'appliedRule': 'appliedRule', 'changeNumber': 200, - 'attributes': {} + 'attributes': {}, + 'properties': {'prop1': 'value1', 'prop2': 'value2'} }); }); From 446777a30436cf2df5910d28ea1cba17ebc0c66d Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 13 Aug 2025 14:26:58 -0300 Subject: [PATCH 12/31] Update test --- splitio_android/test/splitio_android_test.dart | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) 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': {}, }); }); From 8022195c5e1d75bd0aaddd8cb78620bb815409c3 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 13 Aug 2025 14:36:30 -0300 Subject: [PATCH 13/31] Update splitio_ios test --- splitio_ios/ios/Classes/Extensions.swift | 4 +++- splitio_ios/pubspec.yaml | 4 ++-- splitio_ios/test/splitio_ios_test.dart | 18 ++++++++++++------ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/splitio_ios/ios/Classes/Extensions.swift b/splitio_ios/ios/Classes/Extensions.swift index fdad567..d232430 100644 --- a/splitio_ios/ios/Classes/Extensions.swift +++ b/splitio_ios/ios/Classes/Extensions.swift @@ -9,7 +9,8 @@ extension Impression { "time": time, "appliedRule": label, "changeNumber": changeNumber, - "attributes": attributes] + "attributes": attributes, + "properties": properties] } } @@ -26,6 +27,7 @@ extension SplitView { "defaultTreatment": splitView.defaultTreatment, "sets": splitView.sets, "impressionsDisabled": splitView.impressionsDisabled, + "properties": splitView.properties ] } else { return [:] diff --git a/splitio_ios/pubspec.yaml b/splitio_ios/pubspec.yaml index 06849c8..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,7 @@ 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: 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(); From 31901cb89c9bd1ead361a026bcbadc45fef8f551 Mon Sep 17 00:00:00 2001 From: gthea Date: Wed, 13 Aug 2025 15:27:37 -0300 Subject: [PATCH 14/31] EvaluationOptions in dart (#149) --- splitio/example/pubspec.lock | 86 ++++---- splitio/lib/split_client.dart | 88 ++++++--- splitio/pubspec.yaml | 4 +- splitio/test/splitio_client_test.dart | 158 ++++++++++++++- splitio/test/splitio_platform_stub.dart | 57 +++++- .../lib/method_channel_platform.dart | 168 ++++++++++++---- .../lib/split_evaluation_options.dart | 20 ++ .../lib/splitio_platform_interface.dart | 27 ++- splitio_platform_interface/pubspec.yaml | 2 +- .../test/method_channel_platform_test.dart | 185 +++++++++++++++++- .../test/split_evaluation_options_test.dart | 51 +++++ 11 files changed, 714 insertions(+), 132 deletions(-) create mode 100644 splitio_platform_interface/lib/split_evaluation_options.dart create mode 100644 splitio_platform_interface/test/split_evaluation_options_test.dart diff --git a/splitio/example/pubspec.lock b/splitio/example/pubspec.lock index a3269a9..8c2bff7 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,29 +151,29 @@ 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: path: "../../splitio_android" relative: true source: path - version: "0.2.0" + version: "0.3.0-rc.1" splitio_ios: dependency: transitive description: @@ -187,47 +187,47 @@ packages: path: "../../splitio_platform_interface" relative: true source: path - version: "1.5.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: @@ -240,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/pubspec.yaml b/splitio/pubspec.yaml index 73c522c..b771d85 100644 --- a/splitio/pubspec.yaml +++ b/splitio/pubspec.yaml @@ -1,7 +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/ @@ -24,7 +24,7 @@ dependencies: path: ../splitio_android splitio_ios: # ^0.2.0 path: ../splitio_ios - 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/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_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_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/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 0200526..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.6.0-rc.1 +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..5c4b57c 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 + }, }); }); }); 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 {}})); + }); + }); +} From 2b8cef754ec5237519586df0b660dd21fdf23118 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 13 Aug 2025 16:34:25 -0300 Subject: [PATCH 15/31] WIP ios changes --- splitio/example/ios/Podfile.lock | 34 ------- .../xcshareddata/xcschemes/Runner.xcscheme | 3 + splitio/example/ios/Runner/AppDelegate.swift | 2 +- splitio/example/pubspec.lock | 4 +- splitio_ios/example/ios/Podfile | 4 - splitio_ios/example/ios/Podfile.lock | 27 +++--- .../xcshareddata/xcschemes/Runner.xcscheme | 3 + .../example/ios/Runner/AppDelegate.swift | 2 +- .../ios/SplitTests/ExtensionsTests.swift | 33 ++++++- .../example/ios/SplitTests/SplitTests.swift | 92 ++++++++++++++----- splitio_ios/example/pubspec.lock | 84 ++++++++--------- splitio_ios/ios/Classes/Extensions.swift | 2 +- splitio_ios/ios/splitio_ios.podspec | 4 +- 13 files changed, 165 insertions(+), 129 deletions(-) delete mode 100644 splitio/example/ios/Podfile.lock diff --git a/splitio/example/ios/Podfile.lock b/splitio/example/ios/Podfile.lock deleted file mode 100644 index 56de32d..0000000 --- a/splitio/example/ios/Podfile.lock +++ /dev/null @@ -1,34 +0,0 @@ -PODS: - - Flutter (1.0.0) - - Split (3.1.0) - - splitio_ios (0.7.0): - - Flutter - - Split - -DEPENDENCIES: - - Flutter (from `Flutter`) - - Split (from `https://github.com/splitio/ios-client.git`, branch `SDKS-9073_baseline`) - - splitio_ios (from `.symlinks/plugins/splitio_ios/ios`) - -EXTERNAL SOURCES: - Flutter: - :path: Flutter - Split: - :branch: SDKS-9073_baseline - :git: https://github.com/splitio/ios-client.git - splitio_ios: - :path: ".symlinks/plugins/splitio_ios/ios" - -CHECKOUT OPTIONS: - Split: - :commit: 708427ff99d24e2f2ae6e5a672ee23efebafba06 - :git: https://github.com/splitio/ios-client.git - -SPEC CHECKSUMS: - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - Split: 17f15abcc74b39c3a8d670f59e787163626ad6b5 - splitio_ios: 00bf48283a9e3f9497a973d9b5cafc5d414dd427 - -PODFILE CHECKSUM: a52d9df387b5aca8aed91ec989839c51add619b2 - -COCOAPODS: 1.15.0 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 8c2bff7..624c7f2 100644 --- a/splitio/example/pubspec.lock +++ b/splitio/example/pubspec.lock @@ -173,14 +173,14 @@ packages: path: "../../splitio_android" relative: true source: path - version: "0.3.0-rc.1" + version: "1.0.0-rc.1" splitio_ios: dependency: transitive description: path: "../../splitio_ios" relative: true source: path - version: "0.2.0" + version: "1.0.0-rc.1" splitio_platform_interface: dependency: transitive description: diff --git a/splitio_ios/example/ios/Podfile b/splitio_ios/example/ios/Podfile index c4289ff..8629561 100644 --- a/splitio_ios/example/ios/Podfile +++ b/splitio_ios/example/ios/Podfile @@ -32,8 +32,6 @@ target 'Runner' do use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) - - pod 'Split', :git => 'https://github.com/splitio/ios-client.git', :branch => 'SDKS-9073_baseline' # TODO: remove; development only end target 'SplitTests' do @@ -41,8 +39,6 @@ target 'SplitTests' do use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) - - pod 'Split', :git => 'https://github.com/splitio/ios-client.git', :branch => 'SDKS-9073_baseline' # TODO: remove; development only end post_install do |installer| diff --git a/splitio_ios/example/ios/Podfile.lock b/splitio_ios/example/ios/Podfile.lock index 5ee5b26..ea8a88c 100644 --- a/splitio_ios/example/ios/Podfile.lock +++ b/splitio_ios/example/ios/Podfile.lock @@ -1,34 +1,29 @@ PODS: - Flutter (1.0.0) - - Split (3.1.0) - - splitio_ios (0.7.0): + - Split (3.3.2) + - splitio_ios (0.8.0): - Flutter - - Split + - Split (~> 3.3.2) DEPENDENCIES: - Flutter (from `Flutter`) - - Split (from `https://github.com/splitio/ios-client.git`, branch `SDKS-9073_baseline`) - splitio_ios (from `.symlinks/plugins/splitio_ios/ios`) +SPEC REPOS: + trunk: + - Split + EXTERNAL SOURCES: Flutter: :path: Flutter - Split: - :branch: SDKS-9073_baseline - :git: https://github.com/splitio/ios-client.git splitio_ios: :path: ".symlinks/plugins/splitio_ios/ios" -CHECKOUT OPTIONS: - Split: - :commit: 708427ff99d24e2f2ae6e5a672ee23efebafba06 - :git: https://github.com/splitio/ios-client.git - SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - Split: 17f15abcc74b39c3a8d670f59e787163626ad6b5 - splitio_ios: 00bf48283a9e3f9497a973d9b5cafc5d414dd427 + Split: 0d4962a6c15180e1857c1a3753e1ae9c91a6150b + splitio_ios: 438ad21d0dfe467670f8b9508773b77b16a71d6b -PODFILE CHECKSUM: 3e7633332e3580ada5ac333ff9c0b05e8c0b7972 +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 cdb123e..ff7f5f8 100644 --- a/splitio_ios/example/ios/SplitTests/ExtensionsTests.swift +++ b/splitio_ios/example/ios/SplitTests/ExtensionsTests.swift @@ -16,7 +16,7 @@ class ExtensionsTests: XCTestCase { impression.time = 16161616 let impressionMap = impression.toMap() - XCTAssert(impressionMap.count == 8) + XCTAssert(impressionMap.count == 9) XCTAssert(NSDictionary(dictionary: impressionMap).isEqual(to: [ "key": "matching-key", "bucketingKey": "bucketing-key", @@ -25,7 +25,8 @@ class ExtensionsTests: XCTestCase { "appliedRule": "label", "treatment": "on", "split": "my-split", - "time": 16161616])) + "time": 16161616, + "properties": "{}"])) } func testSplitViewMapping() throws { @@ -41,7 +42,12 @@ class ExtensionsTests: XCTestCase { splitView.impressionsDisabled = true let splitViewMap = SplitView.asMap(splitView: splitView) - XCTAssert(splitViewMap.count == 9) + + // Debug: Print actual values + print("DEBUG - SplitView map count: \(splitViewMap.count)") + print("DEBUG - SplitView map: \(splitViewMap)") + + XCTAssert(splitViewMap.count == 10) XCTAssert(NSDictionary(dictionary: splitViewMap).isEqual(to: [ "name": "my-split", "trafficType": "account", @@ -51,6 +57,25 @@ class ExtensionsTests: XCTestCase { "configs": ["key": "value"], "defaultTreatment": "off", "sets": ["set1", "set2"], - "impressionsDisabled": true])) + "impressionsDisabled": true, + "prerequisites": []])) + } + + func testImpressionMappingDebug() throws { + var impression = Impression() + impression.keyName = "matching-key" + impression.feature = "my-split" + impression.changeNumber = 121212 + impression.bucketingKey = "bucketing-key" + impression.attributes = ["age": 25, "name": "John"] + impression.label = "label" + impression.treatment = "on" + impression.time = 16161616 + + let impressionMap = impression.toMap() + + // Debug: Print actual values + print("DEBUG - Impression map count: \(impressionMap.count)") + print("DEBUG - Impression map: \(impressionMap)") } } 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 ec19606..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.2.0" + version: "1.0.0-rc.1" splitio_platform_interface: dependency: transitive description: path: "../../splitio_platform_interface" relative: true source: path - version: "1.5.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 d232430..fcbe78c 100644 --- a/splitio_ios/ios/Classes/Extensions.swift +++ b/splitio_ios/ios/Classes/Extensions.swift @@ -27,7 +27,7 @@ extension SplitView { "defaultTreatment": splitView.defaultTreatment, "sets": splitView.sets, "impressionsDisabled": splitView.impressionsDisabled, - "properties": splitView.properties + "prerequisites": splitView.prerequisites, ] } else { return [:] diff --git a/splitio_ios/ios/splitio_ios.podspec b/splitio_ios/ios/splitio_ios.podspec index 78c16a3..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' # TODO: specify version + s.dependency 'Split', '~> 3.3.2' s.platform = :ios, '9.0' # Flutter.framework does not contain a i386 slice. From d2745926b498ed80129aaf7f9e83801ea0db9894 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 13 Aug 2025 17:53:50 -0300 Subject: [PATCH 16/31] Fixed test --- splitio/example/ios/Podfile | 2 - splitio/example/ios/Podfile.lock | 29 ++++++++++++++ .../ios/SplitTests/ExtensionsTests.swift | 39 +++++++------------ 3 files changed, 43 insertions(+), 27 deletions(-) create mode 100644 splitio/example/ios/Podfile.lock diff --git a/splitio/example/ios/Podfile b/splitio/example/ios/Podfile index 0b6609e..2c068c4 100644 --- a/splitio/example/ios/Podfile +++ b/splitio/example/ios/Podfile @@ -32,8 +32,6 @@ target 'Runner' do use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) - - pod 'Split', :git => 'https://github.com/splitio/ios-client.git', :branch => 'SDKS-9073_baseline' # TODO: remove; development only end post_install do |installer| diff --git a/splitio/example/ios/Podfile.lock b/splitio/example/ios/Podfile.lock new file mode 100644 index 0000000..e004592 --- /dev/null +++ b/splitio/example/ios/Podfile.lock @@ -0,0 +1,29 @@ +PODS: + - Flutter (1.0.0) + - Split (3.3.2) + - splitio_ios (0.8.0): + - Flutter + - Split (~> 3.3.2) + +DEPENDENCIES: + - Flutter (from `Flutter`) + - splitio_ios (from `.symlinks/plugins/splitio_ios/ios`) + +SPEC REPOS: + trunk: + - Split + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + splitio_ios: + :path: ".symlinks/plugins/splitio_ios/ios" + +SPEC CHECKSUMS: + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + Split: 0d4962a6c15180e1857c1a3753e1ae9c91a6150b + splitio_ios: 438ad21d0dfe467670f8b9508773b77b16a71d6b + +PODFILE CHECKSUM: 4e8f8b2be68aeea4c0d5beb6ff1e79fface1d048 + +COCOAPODS: 1.16.2 diff --git a/splitio_ios/example/ios/SplitTests/ExtensionsTests.swift b/splitio_ios/example/ios/SplitTests/ExtensionsTests.swift index ff7f5f8..ea87d72 100644 --- a/splitio_ios/example/ios/SplitTests/ExtensionsTests.swift +++ b/splitio_ios/example/ios/SplitTests/ExtensionsTests.swift @@ -14,6 +14,7 @@ class ExtensionsTests: XCTestCase { impression.label = "label" impression.treatment = "on" impression.time = 16161616 + impression.properties = "{\"myProp\": true}" let impressionMap = impression.toMap() XCTAssert(impressionMap.count == 9) @@ -26,7 +27,7 @@ class ExtensionsTests: XCTestCase { "treatment": "on", "split": "my-split", "time": 16161616, - "properties": "{}"])) + "properties": "{\"myProp\": true}"])) } func testSplitViewMapping() throws { @@ -39,14 +40,20 @@ class ExtensionsTests: XCTestCase { 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) - - // Debug: Print actual values - print("DEBUG - SplitView map count: \(splitViewMap.count)") - print("DEBUG - SplitView map: \(splitViewMap)") - + XCTAssert(splitViewMap.count == 10) XCTAssert(NSDictionary(dictionary: splitViewMap).isEqual(to: [ "name": "my-split", @@ -58,24 +65,6 @@ class ExtensionsTests: XCTestCase { "defaultTreatment": "off", "sets": ["set1", "set2"], "impressionsDisabled": true, - "prerequisites": []])) - } - - func testImpressionMappingDebug() throws { - var impression = Impression() - impression.keyName = "matching-key" - impression.feature = "my-split" - impression.changeNumber = 121212 - impression.bucketingKey = "bucketing-key" - impression.attributes = ["age": 25, "name": "John"] - impression.label = "label" - impression.treatment = "on" - impression.time = 16161616 - - let impressionMap = impression.toMap() - - // Debug: Print actual values - print("DEBUG - Impression map count: \(impressionMap.count)") - print("DEBUG - Impression map: \(impressionMap)") + "prerequisites": [prerequisite]])) } } From 968343fe4766998686b570fc831c7cac6ed18f0d Mon Sep 17 00:00:00 2001 From: gthea Date: Thu, 14 Aug 2025 12:43:55 -0300 Subject: [PATCH 17/31] Evaluation options, prereqs & imp disabled in Android implementation (#150) --- .../main/java/io/split/splitio/Constants.java | 1 + .../io/split/splitio/EvaluationWrapper.java | 17 +- .../split/splitio/SplitMethodParserImpl.java | 90 ++++++--- .../io/split/splitio/SplitWrapperImpl.java | 33 ++-- .../splitio/SplitMethodParserImplTest.java | 178 ++++++++++++++---- .../split/splitio/SplitWrapperImplTest.java | 41 ++-- splitio_android/pubspec.yaml | 4 +- .../test/splitio_android_test.dart | 16 +- splitio_ios/ios/Classes/Extensions.swift | 4 +- splitio_ios/pubspec.yaml | 4 +- splitio_ios/test/splitio_ios_test.dart | 18 +- .../lib/split_impression.dart | 8 +- .../test/method_channel_platform_test.dart | 4 +- .../test/split_impression_test.dart | 3 + 14 files changed, 299 insertions(+), 122 deletions(-) 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/SplitMethodParserImpl.java b/splitio_android/android/src/main/java/io/split/splitio/SplitMethodParserImpl.java index 7631b13..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,8 +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/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 79bcfb8..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,7 @@ 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: 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/ios/Classes/Extensions.swift b/splitio_ios/ios/Classes/Extensions.swift index fdad567..490d398 100644 --- a/splitio_ios/ios/Classes/Extensions.swift +++ b/splitio_ios/ios/Classes/Extensions.swift @@ -9,7 +9,8 @@ extension Impression { "time": time, "appliedRule": label, "changeNumber": changeNumber, - "attributes": attributes] + "attributes": attributes, + "properties": "properties"] // TODO } } @@ -26,6 +27,7 @@ extension SplitView { "defaultTreatment": splitView.defaultTreatment, "sets": splitView.sets, "impressionsDisabled": splitView.impressionsDisabled, + "properties": "" // TODO ] } else { return [:] diff --git a/splitio_ios/pubspec.yaml b/splitio_ios/pubspec.yaml index 06849c8..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,7 @@ 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: 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/lib/split_impression.dart b/splitio_platform_interface/lib/split_impression.dart index 6deec0b..5a5b712 100644 --- a/splitio_platform_interface/lib/split_impression.dart +++ b/splitio_platform_interface/lib/split_impression.dart @@ -7,9 +7,10 @@ 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) { return Impression( @@ -20,11 +21,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), + Map.from(map['properties'] as Map)); } @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/test/method_channel_platform_test.dart b/splitio_platform_interface/test/method_channel_platform_test.dart index 5c4b57c..3692397 100644 --- a/splitio_platform_interface/test/method_channel_platform_test.dart +++ b/splitio_platform_interface/test/method_channel_platform_test.dart @@ -814,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: { @@ -824,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/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); }); } From c1ff30ebb33cad01df29378c9a016bea11e8770f Mon Sep 17 00:00:00 2001 From: gthea Date: Thu, 14 Aug 2025 13:23:25 -0300 Subject: [PATCH 18/31] Evaluation options, prereqs & imp disabled in iOS implementation (#151) --- splitio/example/ios/Podfile | 2 - splitio/example/ios/Podfile.lock | 27 +++--- .../xcshareddata/xcschemes/Runner.xcscheme | 3 + splitio/example/ios/Runner/AppDelegate.swift | 2 +- splitio/example/pubspec.lock | 4 +- splitio_ios/example/ios/Podfile | 4 - splitio_ios/example/ios/Podfile.lock | 27 +++--- .../xcshareddata/xcschemes/Runner.xcscheme | 3 + .../example/ios/Runner/AppDelegate.swift | 2 +- .../ios/SplitTests/ExtensionsTests.swift | 22 ++++- .../example/ios/SplitTests/SplitTests.swift | 92 ++++++++++++++----- splitio_ios/example/pubspec.lock | 84 ++++++++--------- splitio_ios/ios/Classes/Extensions.swift | 4 +- splitio_ios/ios/splitio_ios.podspec | 4 +- 14 files changed, 166 insertions(+), 114 deletions(-) diff --git a/splitio/example/ios/Podfile b/splitio/example/ios/Podfile index 0b6609e..2c068c4 100644 --- a/splitio/example/ios/Podfile +++ b/splitio/example/ios/Podfile @@ -32,8 +32,6 @@ target 'Runner' do use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) - - pod 'Split', :git => 'https://github.com/splitio/ios-client.git', :branch => 'SDKS-9073_baseline' # TODO: remove; development only end post_install do |installer| diff --git a/splitio/example/ios/Podfile.lock b/splitio/example/ios/Podfile.lock index 56de32d..e004592 100644 --- a/splitio/example/ios/Podfile.lock +++ b/splitio/example/ios/Podfile.lock @@ -1,34 +1,29 @@ PODS: - Flutter (1.0.0) - - Split (3.1.0) - - splitio_ios (0.7.0): + - Split (3.3.2) + - splitio_ios (0.8.0): - Flutter - - Split + - Split (~> 3.3.2) DEPENDENCIES: - Flutter (from `Flutter`) - - Split (from `https://github.com/splitio/ios-client.git`, branch `SDKS-9073_baseline`) - splitio_ios (from `.symlinks/plugins/splitio_ios/ios`) +SPEC REPOS: + trunk: + - Split + EXTERNAL SOURCES: Flutter: :path: Flutter - Split: - :branch: SDKS-9073_baseline - :git: https://github.com/splitio/ios-client.git splitio_ios: :path: ".symlinks/plugins/splitio_ios/ios" -CHECKOUT OPTIONS: - Split: - :commit: 708427ff99d24e2f2ae6e5a672ee23efebafba06 - :git: https://github.com/splitio/ios-client.git - SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - Split: 17f15abcc74b39c3a8d670f59e787163626ad6b5 - splitio_ios: 00bf48283a9e3f9497a973d9b5cafc5d414dd427 + Split: 0d4962a6c15180e1857c1a3753e1ae9c91a6150b + splitio_ios: 438ad21d0dfe467670f8b9508773b77b16a71d6b -PODFILE CHECKSUM: a52d9df387b5aca8aed91ec989839c51add619b2 +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 8c2bff7..624c7f2 100644 --- a/splitio/example/pubspec.lock +++ b/splitio/example/pubspec.lock @@ -173,14 +173,14 @@ packages: path: "../../splitio_android" relative: true source: path - version: "0.3.0-rc.1" + version: "1.0.0-rc.1" splitio_ios: dependency: transitive description: path: "../../splitio_ios" relative: true source: path - version: "0.2.0" + version: "1.0.0-rc.1" splitio_platform_interface: dependency: transitive description: diff --git a/splitio_ios/example/ios/Podfile b/splitio_ios/example/ios/Podfile index c4289ff..8629561 100644 --- a/splitio_ios/example/ios/Podfile +++ b/splitio_ios/example/ios/Podfile @@ -32,8 +32,6 @@ target 'Runner' do use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) - - pod 'Split', :git => 'https://github.com/splitio/ios-client.git', :branch => 'SDKS-9073_baseline' # TODO: remove; development only end target 'SplitTests' do @@ -41,8 +39,6 @@ target 'SplitTests' do use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) - - pod 'Split', :git => 'https://github.com/splitio/ios-client.git', :branch => 'SDKS-9073_baseline' # TODO: remove; development only end post_install do |installer| diff --git a/splitio_ios/example/ios/Podfile.lock b/splitio_ios/example/ios/Podfile.lock index 5ee5b26..ea8a88c 100644 --- a/splitio_ios/example/ios/Podfile.lock +++ b/splitio_ios/example/ios/Podfile.lock @@ -1,34 +1,29 @@ PODS: - Flutter (1.0.0) - - Split (3.1.0) - - splitio_ios (0.7.0): + - Split (3.3.2) + - splitio_ios (0.8.0): - Flutter - - Split + - Split (~> 3.3.2) DEPENDENCIES: - Flutter (from `Flutter`) - - Split (from `https://github.com/splitio/ios-client.git`, branch `SDKS-9073_baseline`) - splitio_ios (from `.symlinks/plugins/splitio_ios/ios`) +SPEC REPOS: + trunk: + - Split + EXTERNAL SOURCES: Flutter: :path: Flutter - Split: - :branch: SDKS-9073_baseline - :git: https://github.com/splitio/ios-client.git splitio_ios: :path: ".symlinks/plugins/splitio_ios/ios" -CHECKOUT OPTIONS: - Split: - :commit: 708427ff99d24e2f2ae6e5a672ee23efebafba06 - :git: https://github.com/splitio/ios-client.git - SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - Split: 17f15abcc74b39c3a8d670f59e787163626ad6b5 - splitio_ios: 00bf48283a9e3f9497a973d9b5cafc5d414dd427 + Split: 0d4962a6c15180e1857c1a3753e1ae9c91a6150b + splitio_ios: 438ad21d0dfe467670f8b9508773b77b16a71d6b -PODFILE CHECKSUM: 3e7633332e3580ada5ac333ff9c0b05e8c0b7972 +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 cdb123e..ea87d72 100644 --- a/splitio_ios/example/ios/SplitTests/ExtensionsTests.swift +++ b/splitio_ios/example/ios/SplitTests/ExtensionsTests.swift @@ -14,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", @@ -25,7 +26,8 @@ class ExtensionsTests: XCTestCase { "appliedRule": "label", "treatment": "on", "split": "my-split", - "time": 16161616])) + "time": 16161616, + "properties": "{\"myProp\": true}"])) } func testSplitViewMapping() throws { @@ -38,10 +40,21 @@ class ExtensionsTests: XCTestCase { 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 == 9) + + XCTAssert(splitViewMap.count == 10) XCTAssert(NSDictionary(dictionary: splitViewMap).isEqual(to: [ "name": "my-split", "trafficType": "account", @@ -51,6 +64,7 @@ class ExtensionsTests: XCTestCase { "configs": ["key": "value"], "defaultTreatment": "off", "sets": ["set1", "set2"], - "impressionsDisabled": true])) + "impressionsDisabled": true, + "prerequisites": [prerequisite]])) } } 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 ec19606..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.2.0" + version: "1.0.0-rc.1" splitio_platform_interface: dependency: transitive description: path: "../../splitio_platform_interface" relative: true source: path - version: "1.5.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 490d398..1c8e65e 100644 --- a/splitio_ios/ios/Classes/Extensions.swift +++ b/splitio_ios/ios/Classes/Extensions.swift @@ -10,7 +10,7 @@ extension Impression { "appliedRule": label, "changeNumber": changeNumber, "attributes": attributes, - "properties": "properties"] // TODO + "properties": properties] } } @@ -27,7 +27,7 @@ extension SplitView { "defaultTreatment": splitView.defaultTreatment, "sets": splitView.sets, "impressionsDisabled": splitView.impressionsDisabled, - "properties": "" // TODO + "prerequisites": splitView.prerequisites ] } else { return [:] diff --git a/splitio_ios/ios/splitio_ios.podspec b/splitio_ios/ios/splitio_ios.podspec index 78c16a3..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' # TODO: specify version + s.dependency 'Split', '~> 3.3.2' s.platform = :ios, '9.0' # Flutter.framework does not contain a i386 slice. From c212bb616555bdb8d7feb4f933621b5d299924b2 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 13:33:38 -0300 Subject: [PATCH 19/31] Fixes --- splitio/lib/splitio.dart | 1 + splitio/pubspec.yaml | 4 ++-- splitio_platform_interface/lib/split_impression.dart | 10 ++++++++-- splitio_platform_interface/lib/split_view.dart | 4 ++-- 4 files changed, 13 insertions(+), 6 deletions(-) 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 b771d85..634f85e 100644 --- a/splitio/pubspec.yaml +++ b/splitio/pubspec.yaml @@ -20,9 +20,9 @@ flutter: dependencies: flutter: sdk: flutter - splitio_android: # ^0.2.0 + splitio_android: # ^1.0.0-rc.1 path: ../splitio_android - splitio_ios: # ^0.2.0 + splitio_ios: # ^1.0.0-rc.1 path: ../splitio_ios splitio_platform_interface: # ^2.0.0-rc.1 path: ../splitio_platform_interface diff --git a/splitio_platform_interface/lib/split_impression.dart b/splitio_platform_interface/lib/split_impression.dart index 5a5b712..3bfdf4e 100644 --- a/splitio_platform_interface/lib/split_impression.dart +++ b/splitio_platform_interface/lib/split_impression.dart @@ -7,12 +7,18 @@ class Impression { final String? appliedRule; final num? changeNumber; final Map attributes; - final Map properties; + final Map? properties; Impression(this.key, this.bucketingKey, this.split, this.treatment, this.time, 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?, @@ -22,7 +28,7 @@ class Impression { map['appliedRule'] as String?, map['changeNumber'] as num?, Map.from(map['attributes'] as Map), - Map.from(map['properties'] as Map)); + properties); } @override diff --git a/splitio_platform_interface/lib/split_view.dart b/splitio_platform_interface/lib/split_view.dart index e5f9fc9..9cc2916 100644 --- a/splitio_platform_interface/lib/split_view.dart +++ b/splitio_platform_interface/lib/split_view.dart @@ -18,7 +18,7 @@ class SplitView { String trafficType; bool killed = false; List treatments = []; - int changeNumber; + int? changeNumber; Map configs = {}; String defaultTreatment; List sets = []; @@ -32,7 +32,7 @@ class SplitView { this.impressionsDisabled = false, this.prerequisites = const {}]); - static SplitView? fromEntry(Map? entry) { + static SplitView? fromEntry(Map? entry) { if (entry == null || entry.isEmpty) { return null; } From 72e876b64b41fa25a77487d032fefd186fe39b5f Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 13:55:55 -0300 Subject: [PATCH 20/31] Version name and code for example app --- splitio/example/android/app/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/splitio/example/android/app/build.gradle b/splitio/example/android/app/build.gradle index 947a9f1..25dc71f 100644 --- a/splitio/example/android/app/build.gradle +++ b/splitio/example/android/app/build.gradle @@ -16,6 +16,8 @@ android { applicationId "io.split.splitio_example" minSdkVersion flutter.minSdkVersion targetSdkVersion flutter.targetSdkVersion + versionCode 1 + versionName "1.0" } buildTypes { From 8e2e6d2bd00d8492357c34e76bda082254012e5c Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 14:06:40 -0300 Subject: [PATCH 21/31] Macos 14 in build workflow --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c7c4ca5..6d077cc 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 From b425ba10bcc8bdb520bd630d6d1d753d8a7557fb Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 14:12:08 -0300 Subject: [PATCH 22/31] Macos 14 in test workflow --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6d077cc..0757780 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,7 +55,7 @@ jobs: test-ios: name: Test iOS - runs-on: [ macos-latest ] + runs-on: [ macos-14 ] steps: - uses: actions/checkout@v4 From e81fd5ff27bdb00b277c934b2632c3e34ee65fc8 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 15:45:03 -0300 Subject: [PATCH 23/31] Update platform_interface changelog --- splitio_platform_interface/CHANGELOG.md | 2 ++ splitio_platform_interface/lib/split_view.dart | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/splitio_platform_interface/CHANGELOG.md b/splitio_platform_interface/CHANGELOG.md index 3e5e7e1..50572dd 100644 --- a/splitio_platform_interface/CHANGELOG.md +++ b/splitio_platform_interface/CHANGELOG.md @@ -1,3 +1,5 @@ +# 2.0.0-rc.1 (Aug 14, 2025) + # 1.5.0 (Oct 18, 2024) * Added certificate pinning functionality. This feature allows you to pin a certificate to the SDK, ensuring that the SDK only communicates with servers that present this certificate. Read more in our documentation. diff --git a/splitio_platform_interface/lib/split_view.dart b/splitio_platform_interface/lib/split_view.dart index 9cc2916..8908cf6 100644 --- a/splitio_platform_interface/lib/split_view.dart +++ b/splitio_platform_interface/lib/split_view.dart @@ -32,7 +32,7 @@ class SplitView { this.impressionsDisabled = false, this.prerequisites = const {}]); - static SplitView? fromEntry(Map? entry) { + static SplitView? fromEntry(Map? entry) { if (entry == null || entry.isEmpty) { return null; } From d06b5af053e665ea6f88357e085b7db30dc34a99 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 15:46:32 -0300 Subject: [PATCH 24/31] Update splitio_android --- splitio_android/CHANGELOG.md | 2 ++ splitio_android/pubspec.yaml | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/splitio_android/CHANGELOG.md b/splitio_android/CHANGELOG.md index 4093fab..66affa3 100644 --- a/splitio_android/CHANGELOG.md +++ b/splitio_android/CHANGELOG.md @@ -1,3 +1,5 @@ +# 1.0.0-rc.1 (Aug 14, 2025) + # 0.2.0 (Nov 6, 2024) * Updated Android SDK to `5.0.0` diff --git a/splitio_android/pubspec.yaml b/splitio_android/pubspec.yaml index 05d4e96..5cf2ecd 100644 --- a/splitio_android/pubspec.yaml +++ b/splitio_android/pubspec.yaml @@ -19,8 +19,7 @@ flutter: dependencies: flutter: sdk: flutter - splitio_platform_interface: # ^2.0.0-rc.1 - path: ../splitio_platform_interface + splitio_platform_interface: ^2.0.0-rc.1 dev_dependencies: flutter_test: From df2eba976713084472f7928792a71e6fcbd99ad3 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 16:25:16 -0300 Subject: [PATCH 25/31] Update splitio_ios --- splitio_ios/CHANGELOG.md | 2 ++ splitio_ios/pubspec.yaml | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/splitio_ios/CHANGELOG.md b/splitio_ios/CHANGELOG.md index d8e08de..0b46839 100644 --- a/splitio_ios/CHANGELOG.md +++ b/splitio_ios/CHANGELOG.md @@ -1,3 +1,5 @@ +# 1.0.0-rc.1 (Aug 14, 2025) + # 0.2.0 (Nov 6, 2024) * Updated iOS SDK to `3.0.0` diff --git a/splitio_ios/pubspec.yaml b/splitio_ios/pubspec.yaml index ae0269e..e177e0e 100644 --- a/splitio_ios/pubspec.yaml +++ b/splitio_ios/pubspec.yaml @@ -18,8 +18,7 @@ flutter: dependencies: flutter: sdk: flutter - splitio_platform_interface: # ^2.0.0-rc.1 - path: ../splitio_platform_interface + splitio_platform_interface: ^2.0.0-rc.1 dev_dependencies: flutter_test: From 2a1cf24512cb986590db530257e32f74c59fe7c3 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 16:27:44 -0300 Subject: [PATCH 26/31] Update splitio --- splitio/CHANGELOG.md | 3 +++ splitio/pubspec.yaml | 9 +++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/splitio/CHANGELOG.md b/splitio/CHANGELOG.md index 3ed9258..c23bf23 100644 --- a/splitio/CHANGELOG.md +++ b/splitio/CHANGELOG.md @@ -1,3 +1,6 @@ +# 1.0.0-rc.1 (Aug 14, 2025) + + # 0.2.0 (Nov 6, 2024) * Added support for targeting rules based on large segments. * BREAKING CHANGE (for Split Proxy users): diff --git a/splitio/pubspec.yaml b/splitio/pubspec.yaml index 634f85e..ccf475c 100644 --- a/splitio/pubspec.yaml +++ b/splitio/pubspec.yaml @@ -20,12 +20,9 @@ flutter: dependencies: flutter: sdk: flutter - 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 + splitio_android: ^1.0.0-rc.1 + splitio_ios: ^1.0.0-rc.1 + splitio_platform_interface: ^2.0.0-rc.1 dev_dependencies: flutter_test: sdk: flutter From f411e28dc884b822d5a14672b6f57acdebf37f23 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 16:31:40 -0300 Subject: [PATCH 27/31] Remove publish_to: none --- splitio/pubspec.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/splitio/pubspec.yaml b/splitio/pubspec.yaml index ccf475c..b535c24 100644 --- a/splitio/pubspec.yaml +++ b/splitio/pubspec.yaml @@ -1,4 +1,3 @@ -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: 1.0.0-rc.1 From ec1a001a52918f56febdee12fbf3a8b20c11506d Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 17:01:40 -0300 Subject: [PATCH 28/31] Platform to 2.0.0 --- splitio_platform_interface/CHANGELOG.md | 2 ++ splitio_platform_interface/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/splitio_platform_interface/CHANGELOG.md b/splitio_platform_interface/CHANGELOG.md index 50572dd..cf67b5e 100644 --- a/splitio_platform_interface/CHANGELOG.md +++ b/splitio_platform_interface/CHANGELOG.md @@ -1,3 +1,5 @@ +# 2.0.0 (Aug 14, 2025) + # 2.0.0-rc.1 (Aug 14, 2025) # 1.5.0 (Oct 18, 2024) diff --git a/splitio_platform_interface/pubspec.yaml b/splitio_platform_interface/pubspec.yaml index 3465a95..6b2a0e1 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: 2.0.0-rc.1 +version: 2.0.0 repository: https://github.com/splitio/flutter-sdk-plugin/tree/main/splitio_platform_interface environment: From e4c7d99bafd388935b6b5eb2db8772993c1e0901 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 17:04:25 -0300 Subject: [PATCH 29/31] Update splitio_ios to 1.0.0 --- splitio_android/CHANGELOG.md | 3 +++ splitio_android/pubspec.yaml | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/splitio_android/CHANGELOG.md b/splitio_android/CHANGELOG.md index 66affa3..6f47087 100644 --- a/splitio_android/CHANGELOG.md +++ b/splitio_android/CHANGELOG.md @@ -1,3 +1,6 @@ +# 1.0.0 (Aug 14, 2025) +- Updated Android SDK to `5.3.1`. + # 1.0.0-rc.1 (Aug 14, 2025) # 0.2.0 (Nov 6, 2024) diff --git a/splitio_android/pubspec.yaml b/splitio_android/pubspec.yaml index 5cf2ecd..fc2bbbe 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: 1.0.0-rc.1 +version: 1.0.0 environment: sdk: ">=2.16.2 <4.0.0" @@ -19,7 +19,7 @@ flutter: dependencies: flutter: sdk: flutter - splitio_platform_interface: ^2.0.0-rc.1 + splitio_platform_interface: ^2.0.0 dev_dependencies: flutter_test: From 330ba621890a33b53e7eac5f3677d555a01147c4 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 17:08:42 -0300 Subject: [PATCH 30/31] Update splitio_ios to 1.0.0 --- splitio_ios/CHANGELOG.md | 3 +++ splitio_ios/pubspec.yaml | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/splitio_ios/CHANGELOG.md b/splitio_ios/CHANGELOG.md index 0b46839..d0935b9 100644 --- a/splitio_ios/CHANGELOG.md +++ b/splitio_ios/CHANGELOG.md @@ -1,3 +1,6 @@ +# 1.0.0 (Aug 14, 2025) +- iOS SDK to `3.3.2` + # 1.0.0-rc.1 (Aug 14, 2025) # 0.2.0 (Nov 6, 2024) diff --git a/splitio_ios/pubspec.yaml b/splitio_ios/pubspec.yaml index e177e0e..8c6637b 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: 1.0.0-rc.1 +version: 1.0.0 environment: sdk: ">=2.16.2 <4.0.0" @@ -18,7 +18,7 @@ flutter: dependencies: flutter: sdk: flutter - splitio_platform_interface: ^2.0.0-rc.1 + splitio_platform_interface: ^2.0.0 dev_dependencies: flutter_test: From 175d5f3a15697da5573ccdfc1c6ec10f2ac05aed Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 14 Aug 2025 17:10:11 -0300 Subject: [PATCH 31/31] splitio to 1.0.0 --- splitio/CHANGELOG.md | 9 ++++++++- splitio/pubspec.yaml | 8 ++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/splitio/CHANGELOG.md b/splitio/CHANGELOG.md index c23bf23..057ee4c 100644 --- a/splitio/CHANGELOG.md +++ b/splitio/CHANGELOG.md @@ -1,5 +1,12 @@ -# 1.0.0-rc.1 (Aug 14, 2025) +# 1.0.0 (Aug 14, 2025) +- Updated Android SDK to `5.3.1` & iOS SDK to `3.3.2` +- Added support for rule-based segments. These segments determine membership at runtime by evaluating their configured rules against the user attributes provided to the SDK. +- Added support for feature flag prerequisites. This allows customers to define dependency conditions between flags, which are evaluated before any allowlists or targeting rules. +- Added two new configuration options to control the behavior of the persisted rollout plan cache. Use `rolloutCacheConfiguration` in the config. +- Added a new optional argument to the client `getTreatment` methods to allow passing additional evaluation options, such as a map of properties to append to the generated impressions sent to Split backend. Read more in our docs. +- Added support for the new impressions tracking toggle available on feature flags, both respecting the setting and including the new field being returned on SplitView type objects. Read more in our docs. +# 1.0.0-rc.1 (Aug 14, 2025) # 0.2.0 (Nov 6, 2024) * Added support for targeting rules based on large segments. diff --git a/splitio/pubspec.yaml b/splitio/pubspec.yaml index b535c24..05216c1 100644 --- a/splitio/pubspec.yaml +++ b/splitio/pubspec.yaml @@ -1,6 +1,6 @@ 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: 1.0.0-rc.1 +version: 1.0.0 homepage: https://split.io/ repository: https://github.com/splitio/flutter-sdk-plugin/tree/main/splitio/ @@ -19,9 +19,9 @@ flutter: dependencies: flutter: sdk: flutter - splitio_android: ^1.0.0-rc.1 - splitio_ios: ^1.0.0-rc.1 - splitio_platform_interface: ^2.0.0-rc.1 + splitio_android: ^1.0.0 + splitio_ios: ^1.0.0 + splitio_platform_interface: ^2.0.0 dev_dependencies: flutter_test: sdk: flutter