From 62dfc287559042b90322c6370984119eaf1d0b68 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 12 Aug 2025 17:31:28 -0300 Subject: [PATCH 01/10] 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 02/10] 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 03/10] 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 04/10] 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 05/10] 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 06/10] 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 07/10] 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 08/10] 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 a97b172eda0453408decc7618b012d4e47c5b864 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 13 Aug 2025 17:59:26 -0300 Subject: [PATCH 09/10] Temp value for properties" --- splitio_ios/ios/Classes/Extensions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/splitio_ios/ios/Classes/Extensions.swift b/splitio_ios/ios/Classes/Extensions.swift index d232430..1f2ba2d 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 + "properties": "" // TODO ] } else { return [:] From 07150cbbee56f1eac0c26502350ee6408f741dbc Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Wed, 13 Aug 2025 18:06:45 -0300 Subject: [PATCH 10/10] Temp properties value --- splitio_ios/ios/Classes/Extensions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/splitio_ios/ios/Classes/Extensions.swift b/splitio_ios/ios/Classes/Extensions.swift index 1f2ba2d..490d398 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] + "properties": "properties"] // TODO } }