From 3429a073c3fc03bc8a6785ac715e66d8fc95f40f Mon Sep 17 00:00:00 2001 From: Anton Zhilenkov Date: Wed, 16 Jun 2021 18:01:35 +0300 Subject: [PATCH 01/12] AND-1098 added json value finder --- .../domain/bloc/test_recorder_bloc.dart | 4 +- .../domain/model/json_test_model.dart | 35 +++--- .../domain/model/json_test_model.g.dart | 14 +-- .../domain/test_storages.dart | 6 +- .../domain/common/test_assert_error.dart | 15 +++ lib/app_tester/domain/common/test_error.dart | 26 ++++ .../domain/common/test_executable_error.dart | 69 +++++++++++ lib/app_tester/domain/common/test_result.dart | 20 +++ lib/app_tester/domain/common/typedefs.dart | 24 ++++ .../domain/executable/assert/assert.dart | 44 +++++++ .../executable/assert/assert_launcher.dart | 35 ++++++ .../executable/assert/equals_assert.dart | 21 ++++ .../domain/executable/executable.dart | 9 ++ .../domain/executable/exp_executable.dart | 4 + .../domain/executable/step/step.dart | 99 +++++++++++++++ lib/app_tester/domain/variable_service.dart | 115 ++++++++++++++++++ .../tangem-sdk-flutter/android/build.gradle | 4 +- .../plugin/tangem_sdk/TangemSdkPlugin.kt | 49 ++++++-- .../lib/extensions/any.dart | 4 +- .../lib/extensions/int.dart | 6 + pubspec.yaml | 1 + 21 files changed, 563 insertions(+), 41 deletions(-) create mode 100644 lib/app_tester/domain/common/test_assert_error.dart create mode 100644 lib/app_tester/domain/common/test_error.dart create mode 100644 lib/app_tester/domain/common/test_executable_error.dart create mode 100644 lib/app_tester/domain/common/test_result.dart create mode 100644 lib/app_tester/domain/common/typedefs.dart create mode 100644 lib/app_tester/domain/executable/assert/assert.dart create mode 100644 lib/app_tester/domain/executable/assert/assert_launcher.dart create mode 100644 lib/app_tester/domain/executable/assert/equals_assert.dart create mode 100644 lib/app_tester/domain/executable/executable.dart create mode 100644 lib/app_tester/domain/executable/exp_executable.dart create mode 100644 lib/app_tester/domain/executable/step/step.dart create mode 100644 lib/app_tester/domain/variable_service.dart create mode 100644 plugin/tangem-sdk-flutter/lib/extensions/int.dart diff --git a/lib/app_test_assembler/domain/bloc/test_recorder_bloc.dart b/lib/app_test_assembler/domain/bloc/test_recorder_bloc.dart index b46bbc7..6518e41 100644 --- a/lib/app_test_assembler/domain/bloc/test_recorder_bloc.dart +++ b/lib/app_test_assembler/domain/bloc/test_recorder_bloc.dart @@ -104,7 +104,7 @@ class TestAssembler { } } - TestStep? _createStep(StepRecord record, int index, TestStep stepConfig) { + StepModel? _createStep(StepRecord record, int index, StepModel stepConfig) { final errorMessage = "Can't create a test step for the command: ${record.commandData.type}"; if (!record.commandData.isPrepared()) { onErrorListener?.call("$errorMessage. Command isn't prepared."); @@ -125,7 +125,7 @@ class TestAssembler { final jsonRpc = JSONRPCRequest.fromCommandDataJson(jsonData); final expectedResult = (record.response as TangemSdkResponse).toJson(); - return TestStep( + return StepModel( "$index.${stepConfig.name}.${jsonRpc.method}", jsonRpc.method, jsonRpc.parameters, diff --git a/lib/app_test_assembler/domain/model/json_test_model.dart b/lib/app_test_assembler/domain/model/json_test_model.dart index 83561f2..1bb2d66 100644 --- a/lib/app_test_assembler/domain/model/json_test_model.dart +++ b/lib/app_test_assembler/domain/model/json_test_model.dart @@ -8,7 +8,7 @@ part 'json_test_model.g.dart'; @JsonSerializable() class JsonTest { final TestSetup setup; - final List steps; + final List steps; JsonTest(this.setup, this.steps); @@ -16,10 +16,10 @@ class JsonTest { Map toJson() => _$JsonTestToJson(this); - JsonTest copyWith({TestSetup? setup, List? steps}) => JsonTest( - setup ?? this.setup, - steps ?? this.steps, - ); + JsonTest copyWith({TestSetup? setup, List? steps}) => JsonTest( + setup ?? this.setup, + steps ?? this.steps, + ); } @JsonSerializable() @@ -92,34 +92,39 @@ class ConfigSdk { } @JsonSerializable() -class TestStep { +class StepModel { final String name; final String method; - final Map parameters; + final Map params; final Map expectedResult; final List asserts; final String actionType; final int? iterations; - TestStep( + Map _rawParams = {}; + Map get rawParams => {}..addAll(_rawParams); + + StepModel( this.name, this.method, - this.parameters, + this.params, this.expectedResult, this.asserts, this.actionType, this.iterations, - ); + ) { + this._rawParams.addAll(params); + } - factory TestStep.getDefault() { - return TestStep("stepName", "methodName", {}, {}, [], "NFC_SESSION_RUNNABLE", 1); + factory StepModel.getDefault() { + return StepModel("stepName", "methodName", {}, {}, [], "NFC_SESSION_RUNNABLE", 1); } - factory TestStep.empty(String name, String method) { - return TestStep(name, method, {}, {}, [], "NFC_SESSION_RUNNABLE", 1); + factory StepModel.empty(String name, String method) { + return StepModel(name, method, {}, {}, [], "NFC_SESSION_RUNNABLE", 1); } - factory TestStep.fromJson(Map json) => _$TestStepFromJson(json); + factory StepModel.fromJson(Map json) => _$TestStepFromJson(json); Map toJson() => _$TestStepToJson(this); } diff --git a/lib/app_test_assembler/domain/model/json_test_model.g.dart b/lib/app_test_assembler/domain/model/json_test_model.g.dart index 34ddab9..3534e96 100644 --- a/lib/app_test_assembler/domain/model/json_test_model.g.dart +++ b/lib/app_test_assembler/domain/model/json_test_model.g.dart @@ -9,7 +9,7 @@ part of 'json_test_model.dart'; JsonTest _$JsonTestFromJson(Map json) { return JsonTest( TestSetup.fromJson(json['setup'] as Map), - (json['steps'] as List).map((e) => TestStep.fromJson(e as Map)).toList(), + (json['steps'] as List).map((e) => StepModel.fromJson(e as Map)).toList(), ); } @@ -24,10 +24,10 @@ TestSetup _$TestSetupFromJson(Map json) { json['description'] as String, json['personalizationConfig'] as Map, json['sdkConfig'] == null ? null : ConfigSdk.fromJson(json['sdkConfig'] as Map), - json['minimalFirmware'] == null ? null : FirmwareVersion.fromJson(json['minimalFirmware']), + json['minimalFirmware'] == null ? null : FirmwareVersion.fromJson(json['minimalFirmware'] as String), json['platform'] as String?, json['iterations'] as int?, - json['creationDateMs'] as int, + json['creationDateMs'] as int?, ); } @@ -48,8 +48,8 @@ ConfigSdk _$ConfigSdkFromJson(Map json) { Map _$ConfigSdkToJson(ConfigSdk instance) => {}; -TestStep _$TestStepFromJson(Map json) { - return TestStep( +StepModel _$TestStepFromJson(Map json) { + return StepModel( json['name'] as String, json['method'] as String, json['parameters'] as Map, @@ -60,10 +60,10 @@ TestStep _$TestStepFromJson(Map json) { ); } -Map _$TestStepToJson(TestStep instance) => { +Map _$TestStepToJson(StepModel instance) => { 'name': instance.name, 'method': instance.method, - 'parameters': instance.parameters, + 'parameters': instance.params, 'expectedResult': instance.expectedResult, 'asserts': instance.asserts, 'actionType': instance.actionType, diff --git a/lib/app_test_assembler/domain/test_storages.dart b/lib/app_test_assembler/domain/test_storages.dart index 07be9b5..4deb0b8 100644 --- a/lib/app_test_assembler/domain/test_storages.dart +++ b/lib/app_test_assembler/domain/test_storages.dart @@ -16,12 +16,12 @@ class TestSetupConfigStorage extends ConfigSharedPrefsStorage { TestSetup convertFrom(Map json) => TestSetup.fromJson(json); } -class TestStepConfigStorage extends ConfigSharedPrefsStorage { +class TestStepConfigStorage extends ConfigSharedPrefsStorage { TestStepConfigStorage() : super("testStepConfigStorage"); @override - TestStep getDefaultValue() => TestStep.getDefault(); + StepModel getDefaultValue() => StepModel.getDefault(); @override - TestStep convertFrom(Map json) => TestStep.fromJson(json); + StepModel convertFrom(Map json) => StepModel.fromJson(json); } diff --git a/lib/app_tester/domain/common/test_assert_error.dart b/lib/app_tester/domain/common/test_assert_error.dart new file mode 100644 index 0000000..b3c26a6 --- /dev/null +++ b/lib/app_tester/domain/common/test_assert_error.dart @@ -0,0 +1,15 @@ +import 'package:devkit/app_tester/domain/common/test_error.dart'; +import 'package:sealed_class/sealed_class.dart'; + +@Sealed([EqualsError]) +abstract class TestAssertError implements TestFrameworkError {} + +class EqualsError extends TestAssertError { + final dynamic firstValue; + final dynamic secondValue; + + EqualsError(this.firstValue, this.secondValue); + + @override + String get errorMessage => "Fields doesn't match. f1: $firstValue, f2: $secondValue"; +} diff --git a/lib/app_tester/domain/common/test_error.dart b/lib/app_tester/domain/common/test_error.dart new file mode 100644 index 0000000..9a0fa5e --- /dev/null +++ b/lib/app_tester/domain/common/test_error.dart @@ -0,0 +1,26 @@ +abstract class TestFrameworkError { + String get errorMessage; +} + +// fun TestFrameworkError.toTangemError(): TangemError { +// return object : TangemError { +// override val code: Int = 99999 +// override var customMessage: String = errorMessage +// override val messageResId: Int? = null +// } +// } + +// fun TangemError.toFrameworkError(): TestFrameworkError { +// val code = this.code +// val customMessage = this.customMessage +// return object : TestFrameworkError { +// override val errorMessage: String = "TangemSdkError code: $code, customMessage: $customMessage" +// } +// } + +// sealed class TestError(override val errorMessage: String) : TestFrameworkError { +// class EnvironmentInitError : TestError("Test environment initialization failed") +// class TestIsEmptyError : TestError("Test doesn't contains any data to proceed") +// class StepsIsEmptyError : TestError("Test doesn't contains any steps") +// class SessionSdkInitError(error: TangemError) : TestError("Session initialization failed. Code: ${error.code}, message: ${error.customMessage}") +// } diff --git a/lib/app_tester/domain/common/test_executable_error.dart b/lib/app_tester/domain/common/test_executable_error.dart new file mode 100644 index 0000000..5d99b88 --- /dev/null +++ b/lib/app_tester/domain/common/test_executable_error.dart @@ -0,0 +1,69 @@ +import 'package:devkit/app_tester/domain/common/test_error.dart'; +import 'package:sealed_class/sealed_class.dart'; + +@Sealed([ + ExecutableNotInitialized, + ExecutableNotFoundError, + MissingJsonAdapterError, + FetchVariableError, + UnexpectedResponseError, + ExpectedResultError, +]) +abstract class ExecutableError implements TestFrameworkError {} + +class ExecutableNotInitialized implements ExecutableError { + final String name; + + ExecutableNotInitialized(this.name); + + @override + String get errorMessage => "Executable $name is not initialized"; +} + +class ExecutableNotFoundError implements ExecutableError { + final String name; + + ExecutableNotFoundError(this.name); + + @override + String get errorMessage => "Executable is not found for the name: $name"; +} + +class MissingJsonAdapterError implements ExecutableError { + final String name; + + MissingJsonAdapterError(this.name); + + @override + String get errorMessage => "Missing json runnable adapter for the $name"; +} + +class FetchVariableError implements ExecutableError { + final dynamic paramName; + final String path; + final AssertionError exception; + + FetchVariableError(this.paramName, this.path, this.exception); + + @override + String get errorMessage => + "Fetching variable failed. Name: $paramName, path: $path, ex: ${exception.message?.toString()}"; +} + +class UnexpectedResponseError implements ExecutableError { + final Object response; + + UnexpectedResponseError(this.response); + + @override + String get errorMessage => "Waiting for JsonResponse, but current is ${response.runtimeType.toString()}"; +} + +class ExpectedResultError implements ExecutableError { + final List errorMessages; + + ExpectedResultError(this.errorMessages); + + @override + String get errorMessage => errorMessages.join("\n"); +} diff --git a/lib/app_tester/domain/common/test_result.dart b/lib/app_tester/domain/common/test_result.dart new file mode 100644 index 0000000..b89c03c --- /dev/null +++ b/lib/app_tester/domain/common/test_result.dart @@ -0,0 +1,20 @@ +import 'package:devkit/app_tester/domain/common/test_error.dart'; +import 'package:sealed_class/sealed_class.dart'; + +// part 'test_result.g.dart'; + +// flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs +@Sealed([Success, Failure]) +class TestResult {} + +class Success implements TestResult { + final String? name; + + Success([this.name]); +} + +class Failure implements TestResult { + final TestFrameworkError error; + + Failure(this.error); +} diff --git a/lib/app_tester/domain/common/typedefs.dart b/lib/app_tester/domain/common/typedefs.dart new file mode 100644 index 0000000..58fd49c --- /dev/null +++ b/lib/app_tester/domain/common/typedefs.dart @@ -0,0 +1,24 @@ +import 'package:devkit/app_tester/domain/common/test_error.dart'; +import 'package:devkit/app_tester/domain/common/test_result.dart'; +import 'package:devkit/app_tester/domain/executable/assert/assert.dart'; +import 'package:tangem_sdk/model/json_rpc.dart'; + +typedef SourceMap = Map; +typedef OnComplete = void Function(TestResult result); +typedef OnTestSequenceComplete = void Function(TestFrameworkError? error); +typedef OnStepSequenceComplete = void Function(CardSession, TestResult); + +//TODO: bridge to the TangemSdk.CardSession +abstract class CardSession {} + +abstract class JSONRPCConverter { + JSONRPCConvertible? convert(JSONRPCRequest request); +} + +abstract class AssertsFactory { + Assert? getAssert(String type); +} + +abstract class JSONRPCConvertible { + // fun makeRunnable(params: Map): CardSessionRunnable +} diff --git a/lib/app_tester/domain/executable/assert/assert.dart b/lib/app_tester/domain/executable/assert/assert.dart new file mode 100644 index 0000000..ad26220 --- /dev/null +++ b/lib/app_tester/domain/executable/assert/assert.dart @@ -0,0 +1,44 @@ +import 'dart:core'; + +import 'package:devkit/app_tester/domain/common/typedefs.dart'; +import 'package:devkit/app_tester/domain/executable/executable.dart'; + +abstract class Assert implements Executable { + void init(String parentName, List fields); +} + +abstract class BaseAssert implements Assert { + final String _type; + + late String parentName; + late List fields; + + BaseAssert(this._type); + + @override + String getMethod() { + return _type; + } + + @override + void init(String parentName, List fields) { + this.parentName = parentName; + this.fields = fields; + } + + @override + run(OnComplete callback) { + // if (isInitialized()) { + // callback(TestResult.Failure(ExecutableError.ExecutableNotInitialized(getMethod()))) + // return + // } + runAssert(callback); + } + + runAssert(OnComplete callback); + + dynamic getFieldValue(String pointer) { + return null; + // return VariableService.getValue(parentName, pointer); + } +} diff --git a/lib/app_tester/domain/executable/assert/assert_launcher.dart b/lib/app_tester/domain/executable/assert/assert_launcher.dart new file mode 100644 index 0000000..88a8c47 --- /dev/null +++ b/lib/app_tester/domain/executable/assert/assert_launcher.dart @@ -0,0 +1,35 @@ +import 'dart:collection'; + +import 'package:devkit/app_tester/domain/common/test_result.dart'; +import 'package:devkit/app_tester/domain/common/typedefs.dart'; + +import 'assert.dart'; + +class AssertsLauncher { + final Queue _assertsQueue; + + AssertsLauncher(this._assertsQueue); + + void run(OnComplete callback) { + if (_assertsQueue.isEmpty) { + callback(Success()); + return; + } + + _executeAssert(_assertsQueue.removeFirst(), callback); + } + + void _executeAssert(Assert exAssert, OnComplete callback) { + exAssert.run((result) { + if (result is Success) { + if (_assertsQueue.isEmpty) { + callback(Success()); + } else { + _executeAssert(_assertsQueue.removeFirst(), callback); + } + } else { + callback(result); + } + }); + } +} diff --git a/lib/app_tester/domain/executable/assert/equals_assert.dart b/lib/app_tester/domain/executable/assert/equals_assert.dart new file mode 100644 index 0000000..236888a --- /dev/null +++ b/lib/app_tester/domain/executable/assert/equals_assert.dart @@ -0,0 +1,21 @@ +import 'package:devkit/app_tester/domain/common/test_assert_error.dart'; +import 'package:devkit/app_tester/domain/common/test_result.dart'; +import 'package:devkit/app_tester/domain/common/typedefs.dart'; + +import 'assert.dart'; + +class EqualsAssert extends BaseAssert { + EqualsAssert() : super("EQUALS"); + + @override + runAssert(OnComplete callback) { + final firstValue = getFieldValue(fields[0]); + final secondValue = getFieldValue(fields[1]); + if (firstValue == secondValue) { + callback(Success(getMethod())); + } else { + final error = EqualsError(firstValue, secondValue); + callback(Failure(error)); + } + } +} diff --git a/lib/app_tester/domain/executable/executable.dart b/lib/app_tester/domain/executable/executable.dart new file mode 100644 index 0000000..9d32682 --- /dev/null +++ b/lib/app_tester/domain/executable/executable.dart @@ -0,0 +1,9 @@ +import 'dart:core'; + +import '../common/typedefs.dart'; + +abstract class Executable { + String getMethod(); + + run(OnComplete callback); +} diff --git a/lib/app_tester/domain/executable/exp_executable.dart b/lib/app_tester/domain/executable/exp_executable.dart new file mode 100644 index 0000000..17d63c0 --- /dev/null +++ b/lib/app_tester/domain/executable/exp_executable.dart @@ -0,0 +1,4 @@ +export 'assert/assert.dart'; +export 'assert/assert_launcher.dart'; +export 'assert/equals_assert.dart'; +export 'executable.dart'; diff --git a/lib/app_tester/domain/executable/step/step.dart b/lib/app_tester/domain/executable/step/step.dart new file mode 100644 index 0000000..b29aa2a --- /dev/null +++ b/lib/app_tester/domain/executable/step/step.dart @@ -0,0 +1,99 @@ +import 'dart:collection'; + +import 'package:devkit/app_test_assembler/domain/model/json_test_model.dart'; +import 'package:devkit/app_tester/domain/common/test_executable_error.dart'; +import 'package:devkit/app_tester/domain/common/test_result.dart'; +import 'package:devkit/app_tester/domain/common/typedefs.dart'; +import 'package:devkit/app_tester/domain/executable/assert/assert.dart'; +import 'package:devkit/app_tester/domain/executable/assert/assert_launcher.dart'; +import 'package:devkit/app_tester/domain/executable/executable.dart'; +import 'package:devkit/app_tester/domain/variable_service.dart'; +import 'package:tangem_sdk/tangem_sdk.dart'; + +abstract class Step extends Executable { + int getIterationCount(); + + String getActionType() => "NFC_SESSION_RUNNABLE"; + + void init(CardSession session, JSONRPCConverter jsonRpcConverter, AssertsFactory assertsFactory); +} + +class TestStep extends Step { + final StepModel model; + + late CardSession session; + late JSONRPCConverter jsonRpcConverter; + late AssertsFactory assertsFactory; + + TestStep(this.model); + + @override + int getIterationCount() => model.iterations ?? 1; + + @override + String getMethod() => model.method; + + @override + void init(CardSession session, JSONRPCConverter jsonRpcConverter, AssertsFactory assertsFactory) { + this.session = session; + this.jsonRpcConverter = jsonRpcConverter; + this.assertsFactory = assertsFactory; + } + + @override + run(OnComplete callback) { + _fetchVariables(); + final runnable = jsonRpcConverter.convert(JSONRPCRequest(model.method, model.params)); + if (runnable == null) { + callback(Failure(MissingJsonAdapterError(model.method))); + return; + } + + _runInternal(runnable, callback); + } + + void _runInternal(JSONRPCConvertible runnable, OnComplete callback) { + // runnable.run(session) { result -> + // when (result) { + // is CompletionResult.Success -> { + // val jsonResponse = (result.data as? JSONRPCResponse).guard { + // callback(TestResult.Failure(ExecutableError.UnexpectedResponseError(result.data))) + // return@run + // } + // + // VariableService.registerResult(model.name, jsonResponse) + // executeAsserts(callback) + // } + // is CompletionResult.Failure -> callback(TestResult.Failure(result.error.toFrameworkError())) + // } + // } + } + + void _fetchVariables() { + model.params.clear(); + model.rawParams.forEach((key, value) { + model.params[key] = VariableService.getValue(model.name, value); + }); + } + + void _executeAsserts(OnComplete callback) { + if (model.asserts.isEmpty) { + callback(Success()); + return; + } + + final assertsQueue = Queue(); + model.asserts.forEach((element) { + final exAssert = assertsFactory.getAssert(element.type); + if (exAssert == null) { + callback(Failure(ExecutableNotFoundError(element.type))); + return; + } + + exAssert.init(model.name, element.fields); + assertsQueue.add(exAssert); + }); + + AssertsLauncher(assertsQueue).run(callback); + } +} \ No newline at end of file diff --git a/lib/app_tester/domain/variable_service.dart b/lib/app_tester/domain/variable_service.dart new file mode 100644 index 0000000..1d1ce44 --- /dev/null +++ b/lib/app_tester/domain/variable_service.dart @@ -0,0 +1,115 @@ +import 'package:devkit/app_tester/domain/common/typedefs.dart'; +import 'package:tangem_sdk/extensions/exp_extensions.dart'; +import 'package:tangem_sdk/model/json_rpc.dart'; + +class VariableService { + static final _variablePattern = RegExp("\\{[^\\{\\}]*\\}"); + + static final _bracketLeft = "{"; + static final _bracketRight = "}"; + static final _stepPointer = "#"; + static final _parent = "#parent"; + static final _result = "result"; + + static final _stepValues = {}; + + static void reset() { + _stepValues.clear(); + } + + static void registerResult(String name, JSONRPCResponse response) { + final stepMap = _stepValues[name]; + if (stepMap == null) return; + + stepMap[_result] = response.result; + } + + static void registerStep(String name, SourceMap source) { + _stepValues[name] = source; + } + + static dynamic getValue(String name, dynamic pointer) { + if (pointer == null) { + return null; + } else if (pointer is! String) { + return pointer; + } else if (!_containsVariable(pointer)) { + return pointer; + } else if (_containsStepPointer(pointer)) { + final stepPointer = _extractStepPointer(pointer); + if (stepPointer == null) return null; + + final stepName = stepPointer == _parent ? name : _extractStepName(stepPointer); + final pathValue = _removeBrackets(pointer).replaceAll("$stepPointer.", ""); + final step = _stepValues[stepName]; + return step == null ? null : _getValueByPointer(pathValue, step); + } else { + return _getValueByPointer(pointer, _stepValues[name]); + } + } + + static dynamic _getValueByPointer(String pointer, dynamic target) { + if (target == null) return null; + + return _getValueByPattern(_removeBrackets(pointer).split("\\."), 0, target); + } + + static dynamic _getValueByPattern(List? pointer, int position, dynamic result) { + if (result == null) return null; + if (pointer == null || position >= pointer.length) return result; + + final key = pointer[position]; + if (result is Map) { + return _getValueByPattern(pointer, position + 1, result[key]); + } + + if (result is List) { + if (key.isNumber()) { + final index = int.parse(key.toString()); + if (index >= result.length) { + return null; + } else { + _getValueByPattern(pointer, ++position, result[index]); + } + } else { + final listOfResults = []; + result.forEach((element) { + _getValueByPattern(pointer, position, element)?.let((it) { + listOfResults.add(it); + }); + }); + + return listOfResults.isEmpty ? null : listOfResults; + } + } else { + return result; + } + } + + static bool _containsVariable(String? pointer) { + if (pointer == null || !pointer.contains(_bracketLeft)) return false; + + return _variablePattern.hasMatch(pointer); + } + + static bool _containsStepPointer(String? pointer) { + return pointer == null ? false : pointer.indexOf(_stepPointer) == 1; + } + + static String _extractStepName(String stepPointer) => stepPointer.replaceAll(stepPointer, ""); + + static String? _extractStepPointer(String pointer) => _getPrefix(_removeBrackets(pointer)); + + static String? _getPrefix(String value) { + final suffixIdx = value.indexOf("."); + return suffixIdx < 0 ? null : value.substring(0, suffixIdx); + } + + static String _removeBrackets(String text) { + if (text.startsWith(_bracketLeft) && text.endsWith(_bracketRight)) { + return text.substring(1, text.length - 1); + } else { + return text; + } + } +} diff --git a/plugin/tangem-sdk-flutter/android/build.gradle b/plugin/tangem-sdk-flutter/android/build.gradle index c93048f..540845e 100644 --- a/plugin/tangem-sdk-flutter/android/build.gradle +++ b/plugin/tangem-sdk-flutter/android/build.gradle @@ -52,8 +52,8 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation 'com.tangem:core:develop-37' - implementation 'com.tangem:sdk:develop-37' + implementation 'com.tangem.tangem-sdk-kotlin:core:develop-42' + implementation 'com.tangem.tangem-sdk-kotlin:android:develop-42' implementation "com.squareup.sqldelight:android-driver:1.4.0" diff --git a/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt b/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt index 1db19e2..c207b0c 100644 --- a/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt +++ b/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt @@ -10,7 +10,7 @@ import com.squareup.sqldelight.android.AndroidSqliteDriver import com.tangem.* import com.tangem.commands.common.card.FirmwareType import com.tangem.commands.common.jsonConverter.MoshiJsonConverter -import com.tangem.commands.file.FileData +import com.tangem.commands.file.DataToWrite import com.tangem.commands.file.FileSettingsChange import com.tangem.common.CardValuesDbStorage import com.tangem.common.CardValuesStorage @@ -39,6 +39,8 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { private lateinit var wActivity: WeakReference private lateinit var sdk: TangemSdk + private var cardSession: CardSession? = null + private var replyAlreadySubmit = false override fun onAttachedToActivity(pluginBinding: ActivityPluginBinding) { @@ -112,6 +114,7 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { "readFiles" -> readFiles(call, result) "deleteFiles" -> deleteFiles(call, result) "changeFilesSettings" -> changeFilesSettings(call, result) + "startSession" -> startSession(call, result) "prepareHashes" -> prepareHashes(call, result) "getPlatformVersion" -> result.success("Android ${android.os.Build.VERSION.RELEASE}") else -> result.notImplemented() @@ -355,12 +358,36 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { handleException(result, ex) } } + + private fun startSession(call: MethodCall, result: Result) { + if (cardSession != null) { + handleResult(result, CompletionResult.Success(true)) + return + } + + try { + sdk.startSession( + call.extract("cardId"), + call.extract("initialMessage"), + ) { session, error -> + if (error == null) { + cardSession = session + handleResult(result, CompletionResult.Success(true)) + }else { + cardSession = null + handleResult(result, CompletionResult.Failure(error)) + } + } + } catch (ex: Exception) { + handleException(result, ex) + } + } private fun prepareHashes(call: MethodCall, result: Result) { try { val fileHasData = sdk.prepareHashes( call.extract("cardId"), - call.extract("fileData"), + call.extract("DataToWrite"), call.extract("fileCounter"), call.extract("privateKey"), ) @@ -457,7 +484,7 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { sealed class FileCommand { data class Read(val readPrivateFiles: Boolean = false, val indices: List? = null) - data class Write(val files: List) + data class Write(val files: List) data class Delete(val indices: List?) data class ChangeSettings(val changes: List) } @@ -467,15 +494,15 @@ class MoshiAdapters { class DataToWriteAdapter { @ToJson - fun toJson(src: FileData): String { + fun toJson(src: DataToWrite): String { return when (src) { - is FileData.DataProtectedBySignature -> DataProtectedBySignatureAdapter().toJson(src) - is FileData.DataProtectedByPasscode -> DataProtectedByPasscodeAdapter().toJson(src) + is DataToWrite.DataProtectedBySignature -> DataProtectedBySignatureAdapter().toJson(src) + is DataToWrite.DataProtectedByPasscode -> DataProtectedByPasscodeAdapter().toJson(src) } } @FromJson - fun fromJson(map: MutableMap): FileData { + fun fromJson(map: MutableMap): DataToWrite { return if (map.containsKey("signature")) { DataProtectedBySignatureAdapter().fromJson(map) } else { @@ -486,10 +513,10 @@ class MoshiAdapters { class DataProtectedBySignatureAdapter { @ToJson - fun toJson(src: FileData.DataProtectedBySignature): String = MoshiJsonConverter.default().toJson(src) + fun toJson(src: DataToWrite.DataProtectedBySignature): String = MoshiJsonConverter.default().toJson(src) @FromJson - fun fromJson(map: MutableMap): FileData.DataProtectedBySignature { + fun fromJson(map: MutableMap): DataToWrite.DataProtectedBySignature { val converter = MoshiJsonConverter.default() return converter.fromJson(converter.toJson(map)) !! } @@ -497,10 +524,10 @@ class MoshiAdapters { class DataProtectedByPasscodeAdapter { @ToJson - fun toJson(src: FileData.DataProtectedByPasscode): String = MoshiJsonConverter.default().toJson(src) + fun toJson(src: DataToWrite.DataProtectedByPasscode): String = MoshiJsonConverter.default().toJson(src) @FromJson - fun fromJson(map: MutableMap): FileData.DataProtectedByPasscode { + fun fromJson(map: MutableMap): DataToWrite.DataProtectedByPasscode { val converter = MoshiJsonConverter.default() return converter.fromJson(converter.toJson(map)) !! } diff --git a/plugin/tangem-sdk-flutter/lib/extensions/any.dart b/plugin/tangem-sdk-flutter/lib/extensions/any.dart index 70f820b..15c4862 100644 --- a/plugin/tangem-sdk-flutter/lib/extensions/any.dart +++ b/plugin/tangem-sdk-flutter/lib/extensions/any.dart @@ -15,4 +15,6 @@ extension OnObject on T { final split = this.toString().split('.'); return split.length > 1 && split[0] == this.runtimeType.toString(); } -} \ No newline at end of file + + bool isNumber() => int.tryParse(this.toString()) != null; +} diff --git a/plugin/tangem-sdk-flutter/lib/extensions/int.dart b/plugin/tangem-sdk-flutter/lib/extensions/int.dart new file mode 100644 index 0000000..5a31eca --- /dev/null +++ b/plugin/tangem-sdk-flutter/lib/extensions/int.dart @@ -0,0 +1,6 @@ +extension OnInt on int { + void foreach(void Function(int e) block) { + if (this <= 0) return; + for (var i = 0; i < this; i++) block(i); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 01faf1e..dfad4b0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,6 +45,7 @@ dependencies: camera_camera: ^2.0.1 flutter_image_compress: ^1.0.0 json_annotation: ^4.0.1 + sealed_class: ^3.0.0 # flutter_launcher_icons: ^0.9.0 dependency_overrides: From 1a661c3e4c81f1ce1b2cbfe7c4378beac08bee8a Mon Sep 17 00:00:00 2001 From: Anton Zhilenkov Date: Fri, 18 Jun 2021 01:16:02 +0300 Subject: [PATCH 02/12] AND-1079 added ability to run jsonRpc requests via sessionSdk --- lib/app/resources/keys.dart | 1 + lib/app/ui/widgets/menu/menu.dart | 12 +- ...loc.dart => json_test_assembler_bloc.dart} | 4 +- .../domain/bloc/test_recorder_bloc.dart | 2 +- .../domain/model/json_test_model.dart | 8 +- .../domain/model/json_test_model.g.dart | 8 +- ...n.dart => json_test_assembler_screen.dart} | 41 +-- .../domain/common/test_result.dart | 16 + .../domain/common/typedefs.dart | 7 + lib/app_test_launcher/domain/error/error.dart | 12 + .../domain/error}/test_assert_error.dart | 4 +- .../domain/error/test_error.dart | 21 ++ .../domain/error/test_executable_error.dart | 33 ++ .../domain/executable/assert/assert.dart | 49 +++ .../executable/assert/assert_launcher.dart | 33 ++ .../domain/executable/executable.dart | 5 + .../domain/executable/step/step_launcher.dart | 67 ++++ .../domain/variable_service.dart | 7 +- .../ui/screen/json_test_launcher_screen.dart | 287 ++++++++++++++++++ lib/app_tester/domain/common/test_error.dart | 26 -- .../domain/common/test_executable_error.dart | 69 ----- lib/app_tester/domain/common/test_result.dart | 20 -- lib/app_tester/domain/common/typedefs.dart | 24 -- .../domain/executable/assert/assert.dart | 44 --- .../executable/assert/assert_launcher.dart | 35 --- .../executable/assert/equals_assert.dart | 21 -- .../domain/executable/executable.dart | 9 - .../domain/executable/exp_executable.dart | 4 - .../domain/executable/step/step.dart | 99 ------ lib/commons/common_abstracts.dart | 6 +- lib/navigation/routes.dart | 12 +- .../tangem-sdk-flutter/android/build.gradle | 7 +- .../plugin/tangem_sdk/TangemSdkPlugin.kt | 92 ++++-- .../lib/extensions/exp_extensions.dart | 1 + .../lib/extensions/iterable.dart | 8 + .../lib/model/json_rpc.dart | 6 +- .../lib/model/json_rpc.g.dart | 4 +- plugin/tangem-sdk-flutter/lib/sdk_plugin.dart | 42 ++- .../test/tangem_sdk_test.dart | 5 - pubspec.yaml | 1 - 40 files changed, 712 insertions(+), 440 deletions(-) rename lib/app_test_assembler/domain/bloc/{json_test_list_bloc.dart => json_test_assembler_bloc.dart} (96%) rename lib/app_test_assembler/ui/screen/{json_test_list_screen.dart => json_test_assembler_screen.dart} (71%) create mode 100644 lib/app_test_launcher/domain/common/test_result.dart create mode 100644 lib/app_test_launcher/domain/common/typedefs.dart create mode 100644 lib/app_test_launcher/domain/error/error.dart rename lib/{app_tester/domain/common => app_test_launcher/domain/error}/test_assert_error.dart (69%) create mode 100644 lib/app_test_launcher/domain/error/test_error.dart create mode 100644 lib/app_test_launcher/domain/error/test_executable_error.dart create mode 100644 lib/app_test_launcher/domain/executable/assert/assert.dart create mode 100644 lib/app_test_launcher/domain/executable/assert/assert_launcher.dart create mode 100644 lib/app_test_launcher/domain/executable/executable.dart create mode 100644 lib/app_test_launcher/domain/executable/step/step_launcher.dart rename lib/{app_tester => app_test_launcher}/domain/variable_service.dart (95%) create mode 100644 lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart delete mode 100644 lib/app_tester/domain/common/test_error.dart delete mode 100644 lib/app_tester/domain/common/test_executable_error.dart delete mode 100644 lib/app_tester/domain/common/test_result.dart delete mode 100644 lib/app_tester/domain/common/typedefs.dart delete mode 100644 lib/app_tester/domain/executable/assert/assert.dart delete mode 100644 lib/app_tester/domain/executable/assert/assert_launcher.dart delete mode 100644 lib/app_tester/domain/executable/assert/equals_assert.dart delete mode 100644 lib/app_tester/domain/executable/executable.dart delete mode 100644 lib/app_tester/domain/executable/exp_executable.dart delete mode 100644 lib/app_tester/domain/executable/step/step.dart diff --git a/lib/app/resources/keys.dart b/lib/app/resources/keys.dart index b3f0063..3725278 100644 --- a/lib/app/resources/keys.dart +++ b/lib/app/resources/keys.dart @@ -112,6 +112,7 @@ class ItemName { //hidden static final navigateToTestScreen = "navigateToTestScreen"; static final navigateToJsonTestAssembler = "navigateToJsonTestAssembler"; + static final navigateToJsonTestLauncher = "navigateToJsonTestLauncher"; static final commandJson = "commandJson"; static final responseSuccessJson = "responseSuccessJson"; static final responseErrorJson = "responseErrorJson"; diff --git a/lib/app/ui/widgets/menu/menu.dart b/lib/app/ui/widgets/menu/menu.dart index 9dba103..5542b3e 100644 --- a/lib/app/ui/widgets/menu/menu.dart +++ b/lib/app/ui/widgets/menu/menu.dart @@ -15,6 +15,7 @@ enum MenuItem { personalizationExport, navigateToTestScreen, navigateToJsonTestAssembler, + navigateToJsonTestLauncher, } class Menu { @@ -31,7 +32,10 @@ class Menu { Navigator.of(temporaryContext).pushNamed(Routes.TEST); break; case MenuItem.navigateToJsonTestAssembler: - Navigator.of(temporaryContext).pushNamed(Routes.JSON_TEST_LIST); + Navigator.of(temporaryContext).pushNamed(Routes.JSON_TEST_ASSEMBLER); + break; + case MenuItem.navigateToJsonTestLauncher: + Navigator.of(temporaryContext).pushNamed(Routes.JSON_TEST_LAUNCHER); break; } }, @@ -52,7 +56,13 @@ class Menu { value: MenuItem.navigateToJsonTestAssembler, child: TextWidget("Json tests assembler"), ); + final cardTesterScreenItem = PopupMenuItem( + key: ItemId.btnFrom(ItemName.navigateToJsonTestLauncher), + value: MenuItem.navigateToJsonTestLauncher, + child: TextWidget("Json tests launcher"), + ); menuItemList.add(jsonTestAssemblerScreenItem); + menuItemList.add(cardTesterScreenItem); return menuItemList; }, ); diff --git a/lib/app_test_assembler/domain/bloc/json_test_list_bloc.dart b/lib/app_test_assembler/domain/bloc/json_test_assembler_bloc.dart similarity index 96% rename from lib/app_test_assembler/domain/bloc/json_test_list_bloc.dart rename to lib/app_test_assembler/domain/bloc/json_test_assembler_bloc.dart index 7a839c5..26d9d3e 100644 --- a/lib/app_test_assembler/domain/bloc/json_test_list_bloc.dart +++ b/lib/app_test_assembler/domain/bloc/json_test_assembler_bloc.dart @@ -8,14 +8,14 @@ import 'package:devkit/commons/common_abstracts.dart'; import 'package:rxdart/rxdart.dart'; import 'package:share/share.dart'; -class JsonTestListBloc extends BaseBloc { +class JsonTestAssemblerBloc extends BaseBloc { final bsRecords = BehaviorSubject>(); final StorageRepository _storageRepo; late final JsonTestsStorage _jsonTestsStorage; final _storedJsonTests = []; - JsonTestListBloc(this._storageRepo) { + JsonTestAssemblerBloc(this._storageRepo) { this._jsonTestsStorage = _storageRepo.testsStorage; addSubject(bsRecords); addSubscription(_jsonTestsStorage.isReadyToUseStream.listen(_listenStorageReady)); diff --git a/lib/app_test_assembler/domain/bloc/test_recorder_bloc.dart b/lib/app_test_assembler/domain/bloc/test_recorder_bloc.dart index 6518e41..4f7251f 100644 --- a/lib/app_test_assembler/domain/bloc/test_recorder_bloc.dart +++ b/lib/app_test_assembler/domain/bloc/test_recorder_bloc.dart @@ -128,7 +128,7 @@ class TestAssembler { return StepModel( "$index.${stepConfig.name}.${jsonRpc.method}", jsonRpc.method, - jsonRpc.parameters, + jsonRpc.params, expectedResult, stepConfig.asserts, stepConfig.actionType, diff --git a/lib/app_test_assembler/domain/model/json_test_model.dart b/lib/app_test_assembler/domain/model/json_test_model.dart index 1bb2d66..bbbaf7e 100644 --- a/lib/app_test_assembler/domain/model/json_test_model.dart +++ b/lib/app_test_assembler/domain/model/json_test_model.dart @@ -97,7 +97,7 @@ class StepModel { final String method; final Map params; final Map expectedResult; - final List asserts; + final List asserts; final String actionType; final int? iterations; @@ -130,13 +130,13 @@ class StepModel { } @JsonSerializable() -class TestAssert { +class AssertModel { final String type; final List fields; - TestAssert(this.type, this.fields); + AssertModel(this.type, this.fields); - factory TestAssert.fromJson(Map json) => _$TestAssertFromJson(json); + factory AssertModel.fromJson(Map json) => _$TestAssertFromJson(json); Map toJson() => _$TestAssertToJson(this); } diff --git a/lib/app_test_assembler/domain/model/json_test_model.g.dart b/lib/app_test_assembler/domain/model/json_test_model.g.dart index 3534e96..78db61e 100644 --- a/lib/app_test_assembler/domain/model/json_test_model.g.dart +++ b/lib/app_test_assembler/domain/model/json_test_model.g.dart @@ -54,7 +54,7 @@ StepModel _$TestStepFromJson(Map json) { json['method'] as String, json['parameters'] as Map, json['expectedResult'] as Map, - (json['asserts'] as List).map((e) => TestAssert.fromJson(e as Map)).toList(), + (json['asserts'] as List).map((e) => AssertModel.fromJson(e as Map)).toList(), json['actionType'] as String, json['iterations'] as int?, ); @@ -70,14 +70,14 @@ Map _$TestStepToJson(StepModel instance) => { 'iterations': instance.iterations, }; -TestAssert _$TestAssertFromJson(Map json) { - return TestAssert( +AssertModel _$TestAssertFromJson(Map json) { + return AssertModel( json['type'] as String, (json['fields'] as List).map((e) => e as String).toList(), ); } -Map _$TestAssertToJson(TestAssert instance) => { +Map _$TestAssertToJson(AssertModel instance) => { 'type': instance.type, 'fields': instance.fields, }; diff --git a/lib/app_test_assembler/ui/screen/json_test_list_screen.dart b/lib/app_test_assembler/ui/screen/json_test_assembler_screen.dart similarity index 71% rename from lib/app_test_assembler/ui/screen/json_test_list_screen.dart rename to lib/app_test_assembler/ui/screen/json_test_assembler_screen.dart index eed4f33..79bd334 100644 --- a/lib/app_test_assembler/ui/screen/json_test_list_screen.dart +++ b/lib/app_test_assembler/ui/screen/json_test_assembler_screen.dart @@ -1,6 +1,6 @@ import 'package:devkit/app/ui/screen/card_action/helpers.dart'; import 'package:devkit/app/ui/widgets/app_widgets.dart'; -import 'package:devkit/app_test_assembler/domain/bloc/json_test_list_bloc.dart'; +import 'package:devkit/app_test_assembler/domain/bloc/json_test_assembler_bloc.dart'; import 'package:devkit/app_test_assembler/domain/bloc/test_recorder_bloc.dart'; import 'package:devkit/app_test_assembler/domain/model/json_test_model.dart'; import 'package:devkit/app_test_assembler/ui/screen/json_test_detail_screen.dart'; @@ -11,22 +11,24 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:tangem_sdk/extensions/exp_extensions.dart'; -class JsonTestListScreen extends StatefulWidget { - const JsonTestListScreen({Key? key}) : super(key: key); +class JsonTestAssemblerScreen extends StatefulWidget { + const JsonTestAssemblerScreen({Key? key}) : super(key: key); @override - _JsonTestListScreenState createState() => _JsonTestListScreenState(); + _JsonTestAssemblerScreenState createState() => _JsonTestAssemblerScreenState(); } -class _JsonTestListScreenState extends State { - late JsonTestListBloc _bloc; +class _JsonTestAssemblerScreenState extends State { + late JsonTestAssemblerBloc _bloc; @override Widget build(BuildContext context) { final storageRepo = context.read().storageRepo; return MultiRepositoryProvider( - providers: [RepositoryProvider(create: (context) => JsonTestListBloc(storageRepo).apply((it) => _bloc = it))], - child: JsonTestListFrame(), + providers: [ + RepositoryProvider(create: (context) => JsonTestAssemblerBloc(storageRepo).apply((it) => _bloc = it)) + ], + child: JsonTestAssemblerFrame(), ); } @@ -37,7 +39,7 @@ class _JsonTestListScreenState extends State { } } -class JsonTestListFrame extends StatelessWidget { +class JsonTestAssemblerFrame extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( @@ -45,25 +47,25 @@ class JsonTestListFrame extends StatelessWidget { title: Text("Test assembler"), actions: [StartStopRecordWidget()], ), - body: JsonTestListBody(), + body: JsonTestAssemblerBody(), ); } } -class JsonTestListBody extends StatefulWidget { +class JsonTestAssemblerBody extends StatefulWidget { @override - _JsonTestListBodyState createState() => _JsonTestListBodyState(); + _JsonTestAssemblerBodyState createState() => _JsonTestAssemblerBodyState(); } -class _JsonTestListBodyState extends State { - late JsonTestListBloc _jsonTestListBloc; +class _JsonTestAssemblerBodyState extends State { + late JsonTestAssemblerBloc _jsonTestAssemblerBloc; late TestRecorderBlock _testRecorderBlock; @override void initState() { super.initState(); - _jsonTestListBloc = context.read(); + _jsonTestAssemblerBloc = context.read(); _testRecorderBlock = context.read().testRecorderBloc; } @@ -71,9 +73,9 @@ class _JsonTestListBodyState extends State { Widget build(BuildContext context) { return Stack( children: [ - HiddenSnackBarHandlerWidget([_jsonTestListBloc, _testRecorderBlock]), + HiddenSnackBarHandlerWidget([_jsonTestAssemblerBloc, _testRecorderBlock]), StreamBuilder>( - stream: _jsonTestListBloc.bsRecords.stream, + stream: _jsonTestAssemblerBloc.bsRecords.stream, builder: (context, snapshot) { if (snapshot.data == null) return CenterLoadingText(); if (snapshot.data!.isEmpty) return CenterText("No tests have been created yet"); @@ -91,9 +93,10 @@ class _JsonTestListBodyState extends State { children: [ IconButton( icon: Icon(Icons.delete_outline), - onPressed: _testRecorderBlock.recordIsActive() ? null : () => _jsonTestListBloc.delete(index), + onPressed: + _testRecorderBlock.recordIsActive() ? null : () => _jsonTestAssemblerBloc.delete(index), ), - IconButton(icon: Icon(Icons.ios_share), onPressed: () => _jsonTestListBloc.share(index)), + IconButton(icon: Icon(Icons.ios_share), onPressed: () => _jsonTestAssemblerBloc.share(index)), ], ), ); diff --git a/lib/app_test_launcher/domain/common/test_result.dart b/lib/app_test_launcher/domain/common/test_result.dart new file mode 100644 index 0000000..e93caf0 --- /dev/null +++ b/lib/app_test_launcher/domain/common/test_result.dart @@ -0,0 +1,16 @@ + +import 'package:devkit/app_test_launcher/domain/error/error.dart'; + +class TestResult {} + +class Success implements TestResult { + final String? name; + + Success([this.name]); +} + +class Failure implements TestResult { + final TestFrameworkError error; + + Failure(this.error); +} diff --git a/lib/app_test_launcher/domain/common/typedefs.dart b/lib/app_test_launcher/domain/common/typedefs.dart new file mode 100644 index 0000000..93d9d2d --- /dev/null +++ b/lib/app_test_launcher/domain/common/typedefs.dart @@ -0,0 +1,7 @@ +import 'package:devkit/app_test_launcher/domain/common/test_result.dart'; +import 'package:devkit/app_test_launcher/domain/error/error.dart'; + +typedef SourceMap = Map; +typedef OnComplete = void Function(TestResult result); +typedef OnTestSequenceComplete = void Function(TestFrameworkError? error); +typedef OnStepSequenceComplete = void Function(TestResult result); diff --git a/lib/app_test_launcher/domain/error/error.dart b/lib/app_test_launcher/domain/error/error.dart new file mode 100644 index 0000000..ca932e9 --- /dev/null +++ b/lib/app_test_launcher/domain/error/error.dart @@ -0,0 +1,12 @@ +abstract class TestFrameworkError { + String get errorMessage; +} + +class CustomError implements TestFrameworkError { + final String _customError; + + CustomError(this._customError); + + @override + String get errorMessage => _customError; +} diff --git a/lib/app_tester/domain/common/test_assert_error.dart b/lib/app_test_launcher/domain/error/test_assert_error.dart similarity index 69% rename from lib/app_tester/domain/common/test_assert_error.dart rename to lib/app_test_launcher/domain/error/test_assert_error.dart index b3c26a6..105598b 100644 --- a/lib/app_tester/domain/common/test_assert_error.dart +++ b/lib/app_test_launcher/domain/error/test_assert_error.dart @@ -1,7 +1,5 @@ -import 'package:devkit/app_tester/domain/common/test_error.dart'; -import 'package:sealed_class/sealed_class.dart'; +import 'error.dart'; -@Sealed([EqualsError]) abstract class TestAssertError implements TestFrameworkError {} class EqualsError extends TestAssertError { diff --git a/lib/app_test_launcher/domain/error/test_error.dart b/lib/app_test_launcher/domain/error/test_error.dart new file mode 100644 index 0000000..cf2f811 --- /dev/null +++ b/lib/app_test_launcher/domain/error/test_error.dart @@ -0,0 +1,21 @@ +import 'dart:core'; + +import 'error.dart'; + + +abstract class TestError extends TestFrameworkError {} + +class TestIsEmptyError implements TestError { + @override + String get errorMessage => "Test doesn't contains any data to proceed"; +} + +class SessionSdkInitError implements TestError { + final dynamic error; + + SessionSdkInitError(this.error); + + @override + String get errorMessage => "Session initialization failed. Code: ${error.code}, message: ${error.customMessage}"; +} + diff --git a/lib/app_test_launcher/domain/error/test_executable_error.dart b/lib/app_test_launcher/domain/error/test_executable_error.dart new file mode 100644 index 0000000..c1706c2 --- /dev/null +++ b/lib/app_test_launcher/domain/error/test_executable_error.dart @@ -0,0 +1,33 @@ +import 'error.dart'; + +abstract class ExecutableError implements TestFrameworkError {} + +class ExecutableNotFoundError implements ExecutableError { + final String name; + + ExecutableNotFoundError(this.name); + + @override + String get errorMessage => "Executable is not found for the name: $name"; +} + +class FetchVariableError implements ExecutableError { + final dynamic paramName; + final String path; + final AssertionError exception; + + FetchVariableError(this.paramName, this.path, this.exception); + + @override + String get errorMessage => + "Fetching variable failed. Name: $paramName, path: $path, ex: ${exception.message?.toString()}"; +} + +class ExpectedResultError implements ExecutableError { + final List errorMessages; + + ExpectedResultError(this.errorMessages); + + @override + String get errorMessage => errorMessages.join("\n"); +} diff --git a/lib/app_test_launcher/domain/executable/assert/assert.dart b/lib/app_test_launcher/domain/executable/assert/assert.dart new file mode 100644 index 0000000..1ef160f --- /dev/null +++ b/lib/app_test_launcher/domain/executable/assert/assert.dart @@ -0,0 +1,49 @@ +import 'dart:core'; + +import 'package:devkit/app_test_launcher/domain/common/test_result.dart'; +import 'package:devkit/app_test_launcher/domain/common/typedefs.dart'; +import 'package:devkit/app_test_launcher/domain/error/test_assert_error.dart'; +import 'package:devkit/app_test_launcher/domain/executable/executable.dart'; +import 'package:devkit/app_test_launcher/domain/variable_service.dart'; + +class AssertsFactory { + TestAssert? getAssert(String type) { + return null; + } +} + +abstract class TestAssert implements Executable { + final String _type; + + late String parentName; + late List fields; + + TestAssert(this._type); + + void init(String parentName, List fields) { + this.parentName = parentName; + this.fields = fields; + } + + @override + void run(OnComplete callback); + + dynamic getFieldValue(String pointer) { + return VariableService.getValue(parentName, pointer); + } +} + +class EqualsAssert extends TestAssert { + EqualsAssert() : super("EQUALS"); + + @override + run(OnComplete callback) { + final firstValue = getFieldValue(fields[0]); + final secondValue = getFieldValue(fields[1]); + if (firstValue == secondValue) { + callback(Success(_type)); + } else { + callback(Failure(EqualsError(firstValue, secondValue))); + } + } +} diff --git a/lib/app_test_launcher/domain/executable/assert/assert_launcher.dart b/lib/app_test_launcher/domain/executable/assert/assert_launcher.dart new file mode 100644 index 0000000..9ba25c5 --- /dev/null +++ b/lib/app_test_launcher/domain/executable/assert/assert_launcher.dart @@ -0,0 +1,33 @@ +import 'dart:collection'; + +import 'package:devkit/app_test_launcher/domain/common/test_result.dart'; +import 'package:devkit/app_test_launcher/domain/common/typedefs.dart'; +import 'package:devkit/app_test_launcher/domain/executable/executable.dart'; +import 'package:tangem_sdk/extensions/exp_extensions.dart'; + +import 'assert.dart'; + +class AssertsLauncher implements Executable { + final Queue _assertsQueue; + + AssertsLauncher(this._assertsQueue); + + @override + void run(OnComplete callback) { + _executeAssert(_assertsQueue.poll(), callback); + } + + void _executeAssert(TestAssert? testAssert, OnComplete callback) { + if (testAssert == null) { + callback(Success()); + } else { + testAssert.run((result) { + if (result is Success) { + _executeAssert(_assertsQueue.poll(), callback); + } else { + callback(result); + } + }); + } + } +} diff --git a/lib/app_test_launcher/domain/executable/executable.dart b/lib/app_test_launcher/domain/executable/executable.dart new file mode 100644 index 0000000..1e03f7b --- /dev/null +++ b/lib/app_test_launcher/domain/executable/executable.dart @@ -0,0 +1,5 @@ +import 'package:devkit/app_test_launcher/domain/common/typedefs.dart'; + +abstract class Executable { + void run(OnComplete callback); +} diff --git a/lib/app_test_launcher/domain/executable/step/step_launcher.dart b/lib/app_test_launcher/domain/executable/step/step_launcher.dart new file mode 100644 index 0000000..e8d6556 --- /dev/null +++ b/lib/app_test_launcher/domain/executable/step/step_launcher.dart @@ -0,0 +1,67 @@ +import 'dart:collection'; +import 'dart:convert'; + +import 'package:devkit/app_test_assembler/domain/model/json_test_model.dart'; +import 'package:devkit/app_test_launcher/domain/common/test_result.dart'; +import 'package:devkit/app_test_launcher/domain/common/typedefs.dart'; +import 'package:devkit/app_test_launcher/domain/error/error.dart'; +import 'package:devkit/app_test_launcher/domain/error/test_executable_error.dart'; +import 'package:devkit/app_test_launcher/domain/executable/assert/assert.dart'; +import 'package:devkit/app_test_launcher/domain/executable/assert/assert_launcher.dart'; +import 'package:devkit/app_test_launcher/domain/executable/executable.dart'; +import 'package:devkit/app_test_launcher/domain/variable_service.dart'; +import 'package:tangem_sdk/tangem_sdk.dart'; + +class StepLauncher implements Executable { + final StepModel _model; + final AssertsFactory _assertsFactory; + + StepLauncher(this._model, this._assertsFactory) { + VariableService.registerStep(_model.name, _model.toJson()); + } + + @override + void run(OnComplete callback) { + _fetchVariables(); + + final jsonRpcRequestCallback = Callback((result) { + VariableService.registerResult(_model.name, result); + _executeAsserts(callback); + }, (error) { + callback(Failure(CustomError(jsonEncode(error)))); + }); + + TangemSdk.runJSONRPCRequest(jsonRpcRequestCallback, JSONRPCRequest(_model.method, _model.params)); + } + + void _fetchVariables() { + _model.params.clear(); + _model.rawParams.forEach((key, value) { + _model.params[key] = VariableService.getValue(_model.name, value); + }); + _model.expectedResult.forEach((key, value) { + _model.expectedResult[key] = VariableService.getValue(_model.name, value); + }); + } + + void _executeAsserts(OnComplete callback) { + if (_model.asserts.isEmpty) { + callback(Success()); + return; + } + + final assertsQueue = Queue(); + _model.asserts.forEach((element) { + final testAssert = _assertsFactory.getAssert(element.type); + if (testAssert == null) { + callback(Failure(ExecutableNotFoundError(element.type))); + return; + } + + testAssert.init(_model.name, element.fields); + assertsQueue.add(testAssert); + }); + + AssertsLauncher(assertsQueue).run(callback); + } +} diff --git a/lib/app_tester/domain/variable_service.dart b/lib/app_test_launcher/domain/variable_service.dart similarity index 95% rename from lib/app_tester/domain/variable_service.dart rename to lib/app_test_launcher/domain/variable_service.dart index 1d1ce44..0f0c54c 100644 --- a/lib/app_tester/domain/variable_service.dart +++ b/lib/app_test_launcher/domain/variable_service.dart @@ -1,4 +1,4 @@ -import 'package:devkit/app_tester/domain/common/typedefs.dart'; +import 'package:devkit/app_test_launcher/domain/common/typedefs.dart'; import 'package:tangem_sdk/extensions/exp_extensions.dart'; import 'package:tangem_sdk/model/json_rpc.dart'; @@ -19,7 +19,10 @@ class VariableService { static void registerResult(String name, JSONRPCResponse response) { final stepMap = _stepValues[name]; - if (stepMap == null) return; + if (stepMap == null) { + // Step is not registered + return; + } stepMap[_result] = response.result; } diff --git a/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart b/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart new file mode 100644 index 0000000..c4fc2a2 --- /dev/null +++ b/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart @@ -0,0 +1,287 @@ +import 'dart:async'; +import 'dart:collection'; +import 'dart:convert'; + +import 'package:devkit/app/ui/screen/card_action/helpers.dart'; +import 'package:devkit/app/ui/widgets/app_widgets.dart'; +import 'package:devkit/app_test_assembler/domain/model/json_test_model.dart'; +import 'package:devkit/app_test_assembler/domain/test_storages.dart'; +import 'package:devkit/app_test_launcher/domain/common/test_result.dart'; +import 'package:devkit/app_test_launcher/domain/common/typedefs.dart'; +import 'package:devkit/app_test_launcher/domain/error/error.dart'; +import 'package:devkit/app_test_launcher/domain/error/test_error.dart'; +import 'package:devkit/app_test_launcher/domain/executable/assert/assert.dart'; +import 'package:devkit/app_test_launcher/domain/executable/step/step_launcher.dart'; +import 'package:devkit/app_test_launcher/domain/variable_service.dart'; +import 'package:devkit/commons/common_abstracts.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:tangem_sdk/extensions/exp_extensions.dart'; +import 'package:tangem_sdk/sdk_plugin.dart'; + +class JsonTestLauncherScreen extends StatefulWidget { + const JsonTestLauncherScreen({Key? key}) : super(key: key); + + @override + _JsonTestLauncherScreenState createState() => _JsonTestLauncherScreenState(); +} + +class _JsonTestLauncherScreenState extends State { + late JsonTestLauncherBloc _bloc; + + @override + Widget build(BuildContext context) { + return MultiRepositoryProvider( + providers: [ + RepositoryProvider(create: (context) => JsonTestLauncherBloc().apply((it) => _bloc = it)), + ], + child: JsonTestLauncherFrame(), + ); + } + + @override + void dispose() { + _bloc.dispose(); + super.dispose(); + } +} + +class JsonTestLauncherFrame extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text("Tests")), + body: JsonTestLauncherBody(), + ); + } +} + +class JsonTestLauncherBody extends StatefulWidget { + final bool attachSnackBarHandler; + + const JsonTestLauncherBody({Key? key, this.attachSnackBarHandler = true}) : super(key: key); + + @override + _JsonTestLauncherBodyState createState() => _JsonTestLauncherBodyState(); +} + +class _JsonTestLauncherBodyState extends State { + late JsonTestLauncherBloc _bloc; + + @override + void initState() { + super.initState(); + _bloc = context.read(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + HiddenSnackBarHandlerWidget([_bloc]), + StubStreamBuilder>( + _bloc.bsJsonTests.stream, + (context, data) { + return ListView.separated( + itemCount: _bloc.repo.getList().length, + separatorBuilder: (context, index) => HorizontalDelimiter(), + itemBuilder: (context, index) { + final jsonTest = data[index]; + return ListTile( + title: Text(jsonTest.setup.name), + subtitle: Text(jsonTest.setup.description), + trailing: IconButton( + icon: Icon(Icons.play_arrow), + onPressed: () { + _bloc.launch(jsonTest); + }, + ), + ); + }, + ); + }, + ), + ], + ); + } +} + +class JsonTestLauncherBloc extends BaseBloc { + final JsonTestRepository repo = JsonTestRepository(); + final bsJsonTests = BehaviorSubject>(); + + JsonTestLauncherBloc() { + addSubject(bsJsonTests); + initRepository(); + } + + void initRepository() async { + await repo.init(); + bsJsonTests.add(repo.getList()); + } + + void launch(JsonTest jsonTest) { + _startSession(() async { + final launcher = TestLauncher(jsonTest, AssertsFactory()); + launcher.onTestComplete = (result) { + if (result is Success) { + final message = result.name ?? "Unknown test"; + sendSnackbarMessage("$message is completed"); + } else { + sendSnackbarMessage(result); + } + _stopSession(() {}, _handleError); + }; + launcher.launch(); + }, _handleError); + } + + void _startSession(Function onSuccess, Function(dynamic error) onError) { + TangemSdk.startSession(Callback((success) => onSuccess(), onError), { + TangemSdk.initialMessage: { + TangemSdk.initialMessageHeader: "Session is started", + TangemSdk.initialMessageBody: "COME ON !!!" + } + }); + } + + void _stopSession(Function onSuccess, Function(dynamic error) onError) { + TangemSdk.stopSession(Callback((success) => onSuccess(), _handleError)); + } + + void _handleError(dynamic error) { + sendSnackbarMessage(error); + } +} + +class TestLauncher { + final JsonTest _jsonTest; + final AssertsFactory _assertsFactory; + + OnComplete? onTestComplete; + + Queue _testQueue = Queue(); + Queue _stepQueue = Queue(); + + TestLauncher(this._jsonTest, this._assertsFactory); + + void launch() { + _testQueue = _generateTestQueue(); + + final nextTest = _testQueue.poll(); + if (nextTest == null) { + onTestComplete?.call(Failure(TestIsEmptyError())); + } else { + _runTest(nextTest); + } + } + + void _runTest(JsonTest test) { + VariableService.reset(); + _prepare().then((value) { + _stepQueue = Queue.from(test.steps); + final nextStep = _stepQueue.poll(); + if (nextStep == null) { + _onStepSequenceComplete(Success(test.setup.name)); + } else { + _runStep(nextStep); + } + }).onError((error, stackTrace) { + _handleError(error); + }); + } + + void _onStepComplete(TestResult result) { + if (result is Success) { + final nextStep = _stepQueue.poll(); + if (nextStep == null) { + _onStepSequenceComplete(result); + } else { + _runStep(nextStep); + } + } else { + _onStepSequenceComplete(result); + } + } + + void _runStep(StepModel step) { + StepLauncher(step, _assertsFactory).run(_onStepComplete); + } + + void _onStepSequenceComplete(TestResult result) { + if (result is Success) { + final nextTest = _testQueue.poll(); + if (nextTest == null) { + onTestComplete?.call(Success(_jsonTest.setup.name)); + } else { + _runTest(nextTest); + } + } else { + _handleError(result); + } + } + + void _handleError(dynamic error) { + onTestComplete?.call(Failure(CustomError(jsonEncode(error)))); + } + + Queue _generateTestQueue() { + final tests = []; + if (_jsonTest.setup.iterations == null) { + tests.add(_jsonTest); + } else { + _jsonTest.setup.iterations?.foreach((e) => tests.add(_jsonTest)); + } + return Queue.from(tests); + } + + Future _prepare() { + final completer = Completer(); + _configureSdk(() { + _rePersonalize(() { + completer.complete(); + }, _handleError); + }, _handleError); + return completer.future; + } + + void _configureSdk(Function onSuccess, Function(dynamic error) onError) { + onSuccess(); + } + + void _rePersonalize(Function onSuccess, Function(dynamic error) onError) { + onSuccess(); + } +} + +abstract class JsonTestsRepository { + Future init(); + + List getList(); + + JsonTest? get(String name); +} + +class JsonTestRepository implements JsonTestsRepository { + final JsonTestsStorage storage = JsonTestsStorage(); + + @override + Future init() { + final completer = Completer(); + storage.isReadyToUseStream.listen((event) { + if (event) completer.complete(); + }); + return completer.future; + } + + @override + JsonTest? get(String name) { + return storage.get(name); + } + + @override + List getList() { + return storage.names().map((e) => storage.get(e)).toList().toNullSafe(); + } +} diff --git a/lib/app_tester/domain/common/test_error.dart b/lib/app_tester/domain/common/test_error.dart deleted file mode 100644 index 9a0fa5e..0000000 --- a/lib/app_tester/domain/common/test_error.dart +++ /dev/null @@ -1,26 +0,0 @@ -abstract class TestFrameworkError { - String get errorMessage; -} - -// fun TestFrameworkError.toTangemError(): TangemError { -// return object : TangemError { -// override val code: Int = 99999 -// override var customMessage: String = errorMessage -// override val messageResId: Int? = null -// } -// } - -// fun TangemError.toFrameworkError(): TestFrameworkError { -// val code = this.code -// val customMessage = this.customMessage -// return object : TestFrameworkError { -// override val errorMessage: String = "TangemSdkError code: $code, customMessage: $customMessage" -// } -// } - -// sealed class TestError(override val errorMessage: String) : TestFrameworkError { -// class EnvironmentInitError : TestError("Test environment initialization failed") -// class TestIsEmptyError : TestError("Test doesn't contains any data to proceed") -// class StepsIsEmptyError : TestError("Test doesn't contains any steps") -// class SessionSdkInitError(error: TangemError) : TestError("Session initialization failed. Code: ${error.code}, message: ${error.customMessage}") -// } diff --git a/lib/app_tester/domain/common/test_executable_error.dart b/lib/app_tester/domain/common/test_executable_error.dart deleted file mode 100644 index 5d99b88..0000000 --- a/lib/app_tester/domain/common/test_executable_error.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:devkit/app_tester/domain/common/test_error.dart'; -import 'package:sealed_class/sealed_class.dart'; - -@Sealed([ - ExecutableNotInitialized, - ExecutableNotFoundError, - MissingJsonAdapterError, - FetchVariableError, - UnexpectedResponseError, - ExpectedResultError, -]) -abstract class ExecutableError implements TestFrameworkError {} - -class ExecutableNotInitialized implements ExecutableError { - final String name; - - ExecutableNotInitialized(this.name); - - @override - String get errorMessage => "Executable $name is not initialized"; -} - -class ExecutableNotFoundError implements ExecutableError { - final String name; - - ExecutableNotFoundError(this.name); - - @override - String get errorMessage => "Executable is not found for the name: $name"; -} - -class MissingJsonAdapterError implements ExecutableError { - final String name; - - MissingJsonAdapterError(this.name); - - @override - String get errorMessage => "Missing json runnable adapter for the $name"; -} - -class FetchVariableError implements ExecutableError { - final dynamic paramName; - final String path; - final AssertionError exception; - - FetchVariableError(this.paramName, this.path, this.exception); - - @override - String get errorMessage => - "Fetching variable failed. Name: $paramName, path: $path, ex: ${exception.message?.toString()}"; -} - -class UnexpectedResponseError implements ExecutableError { - final Object response; - - UnexpectedResponseError(this.response); - - @override - String get errorMessage => "Waiting for JsonResponse, but current is ${response.runtimeType.toString()}"; -} - -class ExpectedResultError implements ExecutableError { - final List errorMessages; - - ExpectedResultError(this.errorMessages); - - @override - String get errorMessage => errorMessages.join("\n"); -} diff --git a/lib/app_tester/domain/common/test_result.dart b/lib/app_tester/domain/common/test_result.dart deleted file mode 100644 index b89c03c..0000000 --- a/lib/app_tester/domain/common/test_result.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:devkit/app_tester/domain/common/test_error.dart'; -import 'package:sealed_class/sealed_class.dart'; - -// part 'test_result.g.dart'; - -// flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs -@Sealed([Success, Failure]) -class TestResult {} - -class Success implements TestResult { - final String? name; - - Success([this.name]); -} - -class Failure implements TestResult { - final TestFrameworkError error; - - Failure(this.error); -} diff --git a/lib/app_tester/domain/common/typedefs.dart b/lib/app_tester/domain/common/typedefs.dart deleted file mode 100644 index 58fd49c..0000000 --- a/lib/app_tester/domain/common/typedefs.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:devkit/app_tester/domain/common/test_error.dart'; -import 'package:devkit/app_tester/domain/common/test_result.dart'; -import 'package:devkit/app_tester/domain/executable/assert/assert.dart'; -import 'package:tangem_sdk/model/json_rpc.dart'; - -typedef SourceMap = Map; -typedef OnComplete = void Function(TestResult result); -typedef OnTestSequenceComplete = void Function(TestFrameworkError? error); -typedef OnStepSequenceComplete = void Function(CardSession, TestResult); - -//TODO: bridge to the TangemSdk.CardSession -abstract class CardSession {} - -abstract class JSONRPCConverter { - JSONRPCConvertible? convert(JSONRPCRequest request); -} - -abstract class AssertsFactory { - Assert? getAssert(String type); -} - -abstract class JSONRPCConvertible { - // fun makeRunnable(params: Map): CardSessionRunnable -} diff --git a/lib/app_tester/domain/executable/assert/assert.dart b/lib/app_tester/domain/executable/assert/assert.dart deleted file mode 100644 index ad26220..0000000 --- a/lib/app_tester/domain/executable/assert/assert.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'dart:core'; - -import 'package:devkit/app_tester/domain/common/typedefs.dart'; -import 'package:devkit/app_tester/domain/executable/executable.dart'; - -abstract class Assert implements Executable { - void init(String parentName, List fields); -} - -abstract class BaseAssert implements Assert { - final String _type; - - late String parentName; - late List fields; - - BaseAssert(this._type); - - @override - String getMethod() { - return _type; - } - - @override - void init(String parentName, List fields) { - this.parentName = parentName; - this.fields = fields; - } - - @override - run(OnComplete callback) { - // if (isInitialized()) { - // callback(TestResult.Failure(ExecutableError.ExecutableNotInitialized(getMethod()))) - // return - // } - runAssert(callback); - } - - runAssert(OnComplete callback); - - dynamic getFieldValue(String pointer) { - return null; - // return VariableService.getValue(parentName, pointer); - } -} diff --git a/lib/app_tester/domain/executable/assert/assert_launcher.dart b/lib/app_tester/domain/executable/assert/assert_launcher.dart deleted file mode 100644 index 88a8c47..0000000 --- a/lib/app_tester/domain/executable/assert/assert_launcher.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'dart:collection'; - -import 'package:devkit/app_tester/domain/common/test_result.dart'; -import 'package:devkit/app_tester/domain/common/typedefs.dart'; - -import 'assert.dart'; - -class AssertsLauncher { - final Queue _assertsQueue; - - AssertsLauncher(this._assertsQueue); - - void run(OnComplete callback) { - if (_assertsQueue.isEmpty) { - callback(Success()); - return; - } - - _executeAssert(_assertsQueue.removeFirst(), callback); - } - - void _executeAssert(Assert exAssert, OnComplete callback) { - exAssert.run((result) { - if (result is Success) { - if (_assertsQueue.isEmpty) { - callback(Success()); - } else { - _executeAssert(_assertsQueue.removeFirst(), callback); - } - } else { - callback(result); - } - }); - } -} diff --git a/lib/app_tester/domain/executable/assert/equals_assert.dart b/lib/app_tester/domain/executable/assert/equals_assert.dart deleted file mode 100644 index 236888a..0000000 --- a/lib/app_tester/domain/executable/assert/equals_assert.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:devkit/app_tester/domain/common/test_assert_error.dart'; -import 'package:devkit/app_tester/domain/common/test_result.dart'; -import 'package:devkit/app_tester/domain/common/typedefs.dart'; - -import 'assert.dart'; - -class EqualsAssert extends BaseAssert { - EqualsAssert() : super("EQUALS"); - - @override - runAssert(OnComplete callback) { - final firstValue = getFieldValue(fields[0]); - final secondValue = getFieldValue(fields[1]); - if (firstValue == secondValue) { - callback(Success(getMethod())); - } else { - final error = EqualsError(firstValue, secondValue); - callback(Failure(error)); - } - } -} diff --git a/lib/app_tester/domain/executable/executable.dart b/lib/app_tester/domain/executable/executable.dart deleted file mode 100644 index 9d32682..0000000 --- a/lib/app_tester/domain/executable/executable.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'dart:core'; - -import '../common/typedefs.dart'; - -abstract class Executable { - String getMethod(); - - run(OnComplete callback); -} diff --git a/lib/app_tester/domain/executable/exp_executable.dart b/lib/app_tester/domain/executable/exp_executable.dart deleted file mode 100644 index 17d63c0..0000000 --- a/lib/app_tester/domain/executable/exp_executable.dart +++ /dev/null @@ -1,4 +0,0 @@ -export 'assert/assert.dart'; -export 'assert/assert_launcher.dart'; -export 'assert/equals_assert.dart'; -export 'executable.dart'; diff --git a/lib/app_tester/domain/executable/step/step.dart b/lib/app_tester/domain/executable/step/step.dart deleted file mode 100644 index b29aa2a..0000000 --- a/lib/app_tester/domain/executable/step/step.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'dart:collection'; - -import 'package:devkit/app_test_assembler/domain/model/json_test_model.dart'; -import 'package:devkit/app_tester/domain/common/test_executable_error.dart'; -import 'package:devkit/app_tester/domain/common/test_result.dart'; -import 'package:devkit/app_tester/domain/common/typedefs.dart'; -import 'package:devkit/app_tester/domain/executable/assert/assert.dart'; -import 'package:devkit/app_tester/domain/executable/assert/assert_launcher.dart'; -import 'package:devkit/app_tester/domain/executable/executable.dart'; -import 'package:devkit/app_tester/domain/variable_service.dart'; -import 'package:tangem_sdk/tangem_sdk.dart'; - -abstract class Step extends Executable { - int getIterationCount(); - - String getActionType() => "NFC_SESSION_RUNNABLE"; - - void init(CardSession session, JSONRPCConverter jsonRpcConverter, AssertsFactory assertsFactory); -} - -class TestStep extends Step { - final StepModel model; - - late CardSession session; - late JSONRPCConverter jsonRpcConverter; - late AssertsFactory assertsFactory; - - TestStep(this.model); - - @override - int getIterationCount() => model.iterations ?? 1; - - @override - String getMethod() => model.method; - - @override - void init(CardSession session, JSONRPCConverter jsonRpcConverter, AssertsFactory assertsFactory) { - this.session = session; - this.jsonRpcConverter = jsonRpcConverter; - this.assertsFactory = assertsFactory; - } - - @override - run(OnComplete callback) { - _fetchVariables(); - final runnable = jsonRpcConverter.convert(JSONRPCRequest(model.method, model.params)); - if (runnable == null) { - callback(Failure(MissingJsonAdapterError(model.method))); - return; - } - - _runInternal(runnable, callback); - } - - void _runInternal(JSONRPCConvertible runnable, OnComplete callback) { - // runnable.run(session) { result -> - // when (result) { - // is CompletionResult.Success -> { - // val jsonResponse = (result.data as? JSONRPCResponse).guard { - // callback(TestResult.Failure(ExecutableError.UnexpectedResponseError(result.data))) - // return@run - // } - // - // VariableService.registerResult(model.name, jsonResponse) - // executeAsserts(callback) - // } - // is CompletionResult.Failure -> callback(TestResult.Failure(result.error.toFrameworkError())) - // } - // } - } - - void _fetchVariables() { - model.params.clear(); - model.rawParams.forEach((key, value) { - model.params[key] = VariableService.getValue(model.name, value); - }); - } - - void _executeAsserts(OnComplete callback) { - if (model.asserts.isEmpty) { - callback(Success()); - return; - } - - final assertsQueue = Queue(); - model.asserts.forEach((element) { - final exAssert = assertsFactory.getAssert(element.type); - if (exAssert == null) { - callback(Failure(ExecutableNotFoundError(element.type))); - return; - } - - exAssert.init(model.name, element.fields); - assertsQueue.add(exAssert); - }); - - AssertsLauncher(assertsQueue).run(callback); - } -} \ No newline at end of file diff --git a/lib/commons/common_abstracts.dart b/lib/commons/common_abstracts.dart index c1e733d..85424ef 100644 --- a/lib/commons/common_abstracts.dart +++ b/lib/commons/common_abstracts.dart @@ -59,9 +59,9 @@ abstract class Disposable { dispose(); } -abstract class DisposableBloc extends Disposable {} +abstract class DisposableBloc implements Disposable {} -abstract class BaseBloc extends DisposableBloc with SnackBarStreamHolder { +abstract class AsyncBloc implements DisposableBloc { final _subscriptions = []; final _subjects = []; @@ -78,8 +78,8 @@ abstract class BaseBloc extends DisposableBloc with SnackBarStreamHolder { _subscriptions.forEach((element) => element.cancel()); _subjects.forEach((element) => element.close()); } - } +abstract class BaseBloc with AsyncBloc, SnackBarStreamHolder {} abstract class SnackBarStreamHolder { final PublishSubject _snackbarMessage = PublishSubject(); diff --git a/lib/navigation/routes.dart b/lib/navigation/routes.dart index 86d747e..88e5988 100644 --- a/lib/navigation/routes.dart +++ b/lib/navigation/routes.dart @@ -19,10 +19,11 @@ import 'package:devkit/app/ui/screen/card_action/wallet_create_screen.dart'; import 'package:devkit/app/ui/screen/card_action/wallet_purge_screen.dart'; import 'package:devkit/app/ui/screen/main_screen.dart'; import 'package:devkit/app/ui/screen/response/response_screen.dart'; +import 'package:devkit/app_test_assembler/ui/screen/json_test_assembler_screen.dart'; import 'package:devkit/app_test_assembler/ui/screen/json_test_detail_screen.dart'; -import 'package:devkit/app_test_assembler/ui/screen/json_test_list_screen.dart'; import 'package:devkit/app_test_assembler/ui/screen/test_setup_detail_screen.dart'; import 'package:devkit/app_test_assembler/ui/screen/test_step_detail_screen.dart'; +import 'package:devkit/app_test_launcher/ui/screen/json_test_launcher_screen.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:tangem_sdk/model/sdk.dart'; @@ -30,10 +31,11 @@ import 'package:tangem_sdk/model/sdk.dart'; class Routes { static const MAIN = "/"; static const TEST = "/test"; - static const JSON_TEST_LIST = "/json_test_list"; + static const JSON_TEST_ASSEMBLER = "/json_test_assembler"; static const JSON_TEST_DETAIL = "/json_test_detail"; static const TEST_SETUP_DETAIL = "/test_setup_detail"; static const TEST_STEP_DETAIL = "/test_step_detail"; + static const JSON_TEST_LAUNCHER = "/json_test_launcher"; static const SCAN = "/scan"; static const SIGN = "/sign"; static const PERSONALIZE = "/personalize"; @@ -101,8 +103,8 @@ class Routes { return PageRouteBuilder(pageBuilder: (_, __, ___) => ResponseScreen(arguments: settings.arguments)); case TEST: return MaterialPageRoute(builder: (_) => TestScreen()); - case JSON_TEST_LIST: - return MaterialPageRoute(builder: (_) => JsonTestListScreen()); + case JSON_TEST_ASSEMBLER: + return MaterialPageRoute(builder: (_) => JsonTestAssemblerScreen()); case JSON_TEST_DETAIL: return CupertinoPageRoute( builder: (_) => JsonTestDetailScreen(), @@ -118,6 +120,8 @@ class Routes { builder: (_) => TestStepDetailScreen(), settings: settings, ); + case JSON_TEST_LAUNCHER: + return MaterialPageRoute(builder: (_) => JsonTestLauncherScreen()); default: return MaterialPageRoute( builder: (_) => Scaffold( diff --git a/plugin/tangem-sdk-flutter/android/build.gradle b/plugin/tangem-sdk-flutter/android/build.gradle index 540845e..d6dd1ef 100644 --- a/plugin/tangem-sdk-flutter/android/build.gradle +++ b/plugin/tangem-sdk-flutter/android/build.gradle @@ -18,6 +18,7 @@ rootProject.allprojects { repositories { google() jcenter() + mavenLocal() maven {url "https://nexus.tangem-tech.com/repository/maven-releases/"} maven {url 'https://jitpack.io'} } @@ -52,8 +53,10 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation 'com.tangem.tangem-sdk-kotlin:core:develop-42' - implementation 'com.tangem.tangem-sdk-kotlin:android:develop-42' + // implementation 'com.tangem.tangem-sdk-kotlin:core:develop-42' + // implementation 'com.tangem.tangem-sdk-kotlin:android:develop-42' + implementation 'com.tangem.tangem-sdk-kotlin:core:2.0.0' + implementation 'com.tangem.tangem-sdk-kotlin:android:2.0.0' implementation "com.squareup.sqldelight:android-driver:1.4.0" diff --git a/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt b/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt index c207b0c..b5c664f 100644 --- a/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt +++ b/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt @@ -10,6 +10,7 @@ import com.squareup.sqldelight.android.AndroidSqliteDriver import com.tangem.* import com.tangem.commands.common.card.FirmwareType import com.tangem.commands.common.jsonConverter.MoshiJsonConverter +import com.tangem.commands.common.jsonRpc.JSONRPCResponse import com.tangem.commands.file.DataToWrite import com.tangem.commands.file.FileSettingsChange import com.tangem.common.CardValuesDbStorage @@ -29,6 +30,7 @@ import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result +import java.lang.IllegalStateException import java.lang.ref.WeakReference import java.util.* @@ -84,8 +86,9 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { } override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - val channel = MethodChannel(flutterPluginBinding.flutterEngine.dartExecutor, "tangemSdk") - channel.setMethodCallHandler(this) + val messenger = flutterPluginBinding.binaryMessenger + MethodChannel(messenger, "tangemSdk").setMethodCallHandler(this) + MethodChannel(messenger, "tangemSdk_JSONRPC").setMethodCallHandler(this) } override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { @@ -114,8 +117,10 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { "readFiles" -> readFiles(call, result) "deleteFiles" -> deleteFiles(call, result) "changeFilesSettings" -> changeFilesSettings(call, result) - "startSession" -> startSession(call, result) "prepareHashes" -> prepareHashes(call, result) + "startSession" -> startSession(call, result) + "stopSession" -> stopSession(call, result) + "JSONRPCRequest" -> runJSONRPCRequest(call, result) "getPlatformVersion" -> result.success("Android ${android.os.Build.VERSION.RELEASE}") else -> result.notImplemented() } @@ -358,30 +363,6 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { handleException(result, ex) } } - - private fun startSession(call: MethodCall, result: Result) { - if (cardSession != null) { - handleResult(result, CompletionResult.Success(true)) - return - } - - try { - sdk.startSession( - call.extract("cardId"), - call.extract("initialMessage"), - ) { session, error -> - if (error == null) { - cardSession = session - handleResult(result, CompletionResult.Success(true)) - }else { - cardSession = null - handleResult(result, CompletionResult.Failure(error)) - } - } - } catch (ex: Exception) { - handleException(result, ex) - } - } private fun prepareHashes(call: MethodCall, result: Result) { try { @@ -488,6 +469,63 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { data class Delete(val indices: List?) data class ChangeSettings(val changes: List) } + + private fun startSession(call: MethodCall, result: Result) { + try { + if (cardSession != null && cardSession!!.state == CardSessionState.Active) + throw IllegalStateException("Session already started") + + sdk.startSession( + call.extractOptional("cardId"), + call.extractOptional("initialMessage"), + ) { session, error -> + if (error == null) { + cardSession = session + handleResult(result, CompletionResult.Success(true)) + } else { + cardSession = null + handleResult(result, CompletionResult.Failure(error)) + } + } + } catch (ex: Exception) { + handleException(result, ex) + } + } + + private fun stopSession(call: MethodCall, result: Result) { + try { + val session = cardSession ?: throw UnsupportedOperationException("Session is not started") + session.stop() + cardSession = null + handleResult(result, CompletionResult.Success(true)) + } catch (ex: Exception) { + handleException(result, ex) + } + } + + private fun runJSONRPCRequest(call: MethodCall, result: Result) { + try { + val session = cardSession ?: throw UnsupportedOperationException("Session not started") + + val jsonRpcRequest = call.extract("JSONRPCRequest") + session.run(jsonRpcRequest) { response -> + if (replyAlreadySubmit) return@run + replyAlreadySubmit = true + + val jsonRpcResponse = converter.fromJson(response) + ?: throw IllegalArgumentException("Can't convert string response to JSONRPCResponse") + + if (jsonRpcResponse.error == null) { + handler.post { result.success(response) } + } else { + val error = jsonRpcResponse.error !! + handler.post { result.error("${error.code}", error.message, response) } + } + } + } catch (ex: Exception) { + handleException(result, ex) + } + } } class MoshiAdapters { diff --git a/plugin/tangem-sdk-flutter/lib/extensions/exp_extensions.dart b/plugin/tangem-sdk-flutter/lib/extensions/exp_extensions.dart index 4a60e7f..546e889 100644 --- a/plugin/tangem-sdk-flutter/lib/extensions/exp_extensions.dart +++ b/plugin/tangem-sdk-flutter/lib/extensions/exp_extensions.dart @@ -1,4 +1,5 @@ export 'package:tangem_sdk/extensions/any.dart'; +export 'package:tangem_sdk/extensions/int.dart'; export 'package:tangem_sdk/extensions/iterable.dart'; export 'package:tangem_sdk/extensions/list.dart'; export 'package:tangem_sdk/extensions/map.dart'; diff --git a/plugin/tangem-sdk-flutter/lib/extensions/iterable.dart b/plugin/tangem-sdk-flutter/lib/extensions/iterable.dart index f295a74..cf6481f 100644 --- a/plugin/tangem-sdk-flutter/lib/extensions/iterable.dart +++ b/plugin/tangem-sdk-flutter/lib/extensions/iterable.dart @@ -1,3 +1,5 @@ +import 'dart:collection'; + extension OnIterableNullSafe on Iterable { E? firstWhereOrNull(bool test(E e)) { for (E element in this) { @@ -19,3 +21,9 @@ extension OnIterable on Iterable? { return this!.isEmpty; } } + +extension OnQueue on Queue { + E? poll() { + return isEmpty ? null : this.removeFirst(); + } +} diff --git a/plugin/tangem-sdk-flutter/lib/model/json_rpc.dart b/plugin/tangem-sdk-flutter/lib/model/json_rpc.dart index bb99d0c..c729d52 100644 --- a/plugin/tangem-sdk-flutter/lib/model/json_rpc.dart +++ b/plugin/tangem-sdk-flutter/lib/model/json_rpc.dart @@ -15,7 +15,7 @@ abstract class JSONRPC { switch (commandType) { case TangemSdk.cScanCard: { - return TangemSdk.cScanCard; + return "SCAN_TASK"; } case TangemSdk.cSign: { @@ -101,9 +101,9 @@ abstract class JSONRPC { @JsonSerializable() class JSONRPCRequest extends JSONRPC { final String method; - final Map parameters; + final Map params; - JSONRPCRequest(this.method, this.parameters, [dynamic id, String jsonrpc = "2.0"]) : super(id, jsonrpc); + JSONRPCRequest(this.method, this.params, [dynamic id, String jsonrpc = "2.0"]) : super(id, jsonrpc); Map toJson() => _$JSONRPCRequestToJson(this); diff --git a/plugin/tangem-sdk-flutter/lib/model/json_rpc.g.dart b/plugin/tangem-sdk-flutter/lib/model/json_rpc.g.dart index 72bb2d0..625cac4 100644 --- a/plugin/tangem-sdk-flutter/lib/model/json_rpc.g.dart +++ b/plugin/tangem-sdk-flutter/lib/model/json_rpc.g.dart @@ -9,7 +9,7 @@ part of 'json_rpc.dart'; JSONRPCRequest _$JSONRPCRequestFromJson(Map json) { return JSONRPCRequest( json['method'] as String, - json['parameters'] as Map, + json['params'] as Map, json['id'], json['jsonrpc'] as String, ); @@ -19,7 +19,7 @@ Map _$JSONRPCRequestToJson(JSONRPCRequest instance) => json) { diff --git a/plugin/tangem-sdk-flutter/lib/sdk_plugin.dart b/plugin/tangem-sdk-flutter/lib/sdk_plugin.dart index 2a81d8e..85f7351 100644 --- a/plugin/tangem-sdk-flutter/lib/sdk_plugin.dart +++ b/plugin/tangem-sdk-flutter/lib/sdk_plugin.dart @@ -14,7 +14,8 @@ import 'model/sdk.dart'; class TangemSdk { static const commandType = "commandType"; - static const cStartSessionWithJsonRunnable = 'startSessionWithJsonRunnable'; + static const cStartSession = 'startSession'; + static const cStopSession = 'stopSession'; static const cScanCard = 'scanCard'; static const cSign = 'sign'; static const cPersonalize = 'personalize'; @@ -35,6 +36,7 @@ class TangemSdk { static const cDeleteFiles = 'deleteFiles'; static const cChangeFilesSettings = 'changeFilesSettings'; static const cPrepareHashes = "prepareHashes"; + static const cJsonRpcRequest = 'JSONRPCRequest'; static const isAllowedOnlyDebugCards = "isAllowedOnlyDebugCards"; static const cid = "cardId"; @@ -69,12 +71,24 @@ class TangemSdk { static const counter = "counter"; static const changes = "changes"; static const privateKey = "privateKey"; + static const jsonRpcRequest = 'JSONRPCRequest'; + static const jsonRpcResponse = 'JSONRPCResponse'; static const MethodChannel _channel = const MethodChannel('tangemSdk'); + static const MethodChannel _channelJSONRPC = const MethodChannel('tangemSdk_JSONRPC'); - static Future get platformVersion async { - final String version = await _channel.invokeMethod('getPlatformVersion'); - return version; + static Future startSession(Callback callback, [Map valuesToExport = const {}]) async { + _channel + .invokeMethod(cStartSession, valuesToExport) + .then((result) => callback.onSuccess(_createResponse(cStartSession, result))) + .catchError((error) => _sendBackError(callback, error)); + } + + static Future stopSession(Callback callback, [Map valuesToExport = const {}]) async { + _channel + .invokeMethod(cStopSession, valuesToExport) + .then((result) => callback.onSuccess(_createResponse(cStopSession, result))) + .catchError((error) => _sendBackError(callback, error)); } static Future allowsOnlyDebugCards(bool isAllowed) { @@ -96,7 +110,7 @@ class TangemSdk { final type = commandJsonMap[commandType]; final jsonRpc = JSONRPCRequest.fromCommandDataJson(commandJsonMap); _channel - .invokeMethod(cStartSessionWithJsonRunnable, jsonRpc.toJson()) + .invokeMethod(cJsonRpcRequest, jsonRpc.toJson()) .then((result) => callback.onSuccess(_createResponse(type, result))) .catchError((error) => _sendBackError(callback, error)); }); @@ -343,7 +357,9 @@ class TangemSdk { final map = json.decode(jsonString); if (map["code"] == 50002) { callback.onError(UserCancelledError(map['localizedDescription'])); - } else { + } else if (map["jsonrpc"] != null) { + callback.onError(SdkPluginError(jsonString)); + }else { callback.onError(SdkPluginError(map['localizedDescription'])); } } else if (error is Exception) { @@ -352,6 +368,20 @@ class TangemSdk { callback.onError(SdkPluginError("Unknown plugin error: ${error.toString()}")); } } + + + static Future runJSONRPCRequest(Callback callback, JSONRPCRequest request) async{ + final mapWithRequest = {"JSONRPCRequest": jsonEncode(request.toJson())}; + _channelJSONRPC + .invokeMethod(cJsonRpcRequest, mapWithRequest) + .then((result) => callback.onSuccess(_createJSONRPCResponse(result))) + .catchError((error) => _sendBackError(callback, error)); + } + + static dynamic _createJSONRPCResponse(dynamic response) { + final jsonResponse = jsonDecode(response); + return JSONRPCResponse.fromJson(jsonResponse); + } } abstract class TangemSdkBaseError implements Exception { diff --git a/plugin/tangem-sdk-flutter/test/tangem_sdk_test.dart b/plugin/tangem-sdk-flutter/test/tangem_sdk_test.dart index 6759b91..0d86cc2 100644 --- a/plugin/tangem-sdk-flutter/test/tangem_sdk_test.dart +++ b/plugin/tangem-sdk-flutter/test/tangem_sdk_test.dart @@ -1,6 +1,5 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:tangem_sdk/tangem_sdk.dart'; void main() { const MethodChannel channel = MethodChannel('tangem_sdk'); @@ -16,8 +15,4 @@ void main() { tearDown(() { channel.setMockMethodCallHandler(null); }); - - test('getPlatformVersion', () async { - expect(await TangemSdk.platformVersion, '42'); - }); } diff --git a/pubspec.yaml b/pubspec.yaml index dfad4b0..01faf1e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,7 +45,6 @@ dependencies: camera_camera: ^2.0.1 flutter_image_compress: ^1.0.0 json_annotation: ^4.0.1 - sealed_class: ^3.0.0 # flutter_launcher_icons: ^0.9.0 dependency_overrides: From 422724561f42cbb0dde207ff860aa34815164fdd Mon Sep 17 00:00:00 2001 From: Anton Zhilenkov Date: Fri, 18 Jun 2021 01:29:03 +0300 Subject: [PATCH 03/12] AND-1079 fixed typos --- plugin/tangem-sdk-flutter/android/build.gradle | 6 ++---- .../flutter/plugin/tangem_sdk/TangemSdkPlugin.kt | 12 ++++++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/plugin/tangem-sdk-flutter/android/build.gradle b/plugin/tangem-sdk-flutter/android/build.gradle index d6dd1ef..a85387d 100644 --- a/plugin/tangem-sdk-flutter/android/build.gradle +++ b/plugin/tangem-sdk-flutter/android/build.gradle @@ -53,10 +53,8 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - // implementation 'com.tangem.tangem-sdk-kotlin:core:develop-42' - // implementation 'com.tangem.tangem-sdk-kotlin:android:develop-42' - implementation 'com.tangem.tangem-sdk-kotlin:core:2.0.0' - implementation 'com.tangem.tangem-sdk-kotlin:android:2.0.0' + implementation 'com.tangem.tangem-sdk-kotlin:core:develop-42' + implementation 'com.tangem.tangem-sdk-kotlin:android:develop-42' implementation "com.squareup.sqldelight:android-driver:1.4.0" diff --git a/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt b/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt index b5c664f..16a4345 100644 --- a/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt +++ b/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt @@ -368,7 +368,7 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { try { val fileHasData = sdk.prepareHashes( call.extract("cardId"), - call.extract("DataToWrite"), + call.extract("fileData"), call.extract("fileCounter"), call.extract("privateKey"), ) @@ -473,7 +473,7 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { private fun startSession(call: MethodCall, result: Result) { try { if (cardSession != null && cardSession!!.state == CardSessionState.Active) - throw IllegalStateException("Session already started") + throw IllegalStateException("The CardSession has already started") sdk.startSession( call.extractOptional("cardId"), @@ -494,7 +494,7 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { private fun stopSession(call: MethodCall, result: Result) { try { - val session = cardSession ?: throw UnsupportedOperationException("Session is not started") + val session = cardSession ?: throw UnsupportedOperationException("Session not started") session.stop() cardSession = null handleResult(result, CompletionResult.Success(true)) @@ -507,13 +507,13 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { try { val session = cardSession ?: throw UnsupportedOperationException("Session not started") - val jsonRpcRequest = call.extract("JSONRPCRequest") - session.run(jsonRpcRequest) { response -> + val stringOfJSONRPCRequest = call.extract("JSONRPCRequest") + session.run(stringOfJSONRPCRequest) { response -> if (replyAlreadySubmit) return@run replyAlreadySubmit = true val jsonRpcResponse = converter.fromJson(response) - ?: throw IllegalArgumentException("Can't convert string response to JSONRPCResponse") + ?: throw IllegalArgumentException("Can't convert the string response to JSONRPCResponse") if (jsonRpcResponse.error == null) { handler.post { result.success(response) } From ec0b18e6dcaf1fd0cfddb72b1a3e2075fc2aedd8 Mon Sep 17 00:00:00 2001 From: Anton Zhilenkov Date: Mon, 21 Jun 2021 16:06:04 +0300 Subject: [PATCH 04/12] AND-1135 made a difference between errors going from the tangemSdk, kotlinPlugin and flutterPlugin --- lib/app/domain/actions_bloc/abstracts.dart | 6 +- lib/app/domain/actions_bloc/test_bloc.dart | 14 ++-- .../ui/screen/card_action/test_screen.dart | 4 +- .../domain/bloc/test_recorder_bloc.dart | 6 +- .../tangem-sdk-flutter/android/build.gradle | 4 +- .../plugin/tangem_sdk/TangemSdkPlugin.kt | 39 +++++++---- .../tangem-sdk-flutter/example/lib/main.dart | 3 +- .../tangem-sdk-flutter/lib/plugin_error.dart | 65 +++++++++++++++++++ plugin/tangem-sdk-flutter/lib/sdk_plugin.dart | 56 ++-------------- plugin/tangem-sdk-flutter/lib/tangem_sdk.dart | 1 + 10 files changed, 119 insertions(+), 79 deletions(-) create mode 100644 plugin/tangem-sdk-flutter/lib/plugin_error.dart diff --git a/lib/app/domain/actions_bloc/abstracts.dart b/lib/app/domain/actions_bloc/abstracts.dart index f6b3a05..b690931 100644 --- a/lib/app/domain/actions_bloc/abstracts.dart +++ b/lib/app/domain/actions_bloc/abstracts.dart @@ -13,7 +13,7 @@ abstract class ActionBloc extends BaseBloc { final PublishSubject _commandDataIsReady = PublishSubject(); final PublishSubject _successResponse = PublishSubject(); - final PublishSubject _errorResponse = PublishSubject(); + final PublishSubject _errorResponse = PublishSubject(); ActionBloc() { addSubscription(bsCid.stream.listen((event) => _cid = event)); @@ -23,7 +23,7 @@ abstract class ActionBloc extends BaseBloc { Stream get successResponseStream => _successResponse.stream; - Stream get errorResponseStream => _errorResponse.stream; + Stream get errorResponseStream => _errorResponse.stream; Callback get callback => Callback((success) => sendSuccess(success), (error) => sendError(error)); @@ -31,7 +31,7 @@ abstract class ActionBloc extends BaseBloc { _successResponse.add(success); } - sendError(TangemSdkBaseError? error) { + sendError(TangemSdkPluginError? error) { _errorResponse.add(error); } diff --git a/lib/app/domain/actions_bloc/test_bloc.dart b/lib/app/domain/actions_bloc/test_bloc.dart index a96dff5..685a36b 100644 --- a/lib/app/domain/actions_bloc/test_bloc.dart +++ b/lib/app/domain/actions_bloc/test_bloc.dart @@ -22,7 +22,7 @@ class TestBlock extends ActionBloc { invokeAction() async { _clearFields(); if (_inputedCommand.isNullOrEmpty()) { - sendError(TangemSdkError("Input the command json first")); + sendError(PluginFlutterError("Input the command json first")); return; } @@ -31,11 +31,11 @@ class TestBlock extends ActionBloc { _commandType = _command![TangemSdk.commandType]; if (_commandType == null) { - sendError(TangemSdkError("Missing the commandType attribute")); + sendError(PluginFlutterError("Missing the commandType attribute")); return; } } catch (e) { - sendError(TangemSdkError("Json conversion error: $e")); + sendError(PluginFlutterError("Json conversion error: $e")); return; } @@ -43,10 +43,10 @@ class TestBlock extends ActionBloc { createCommandData((commandData) { TangemSdk.runCommand(callback, commandData); }, (errorMessage) { - sendError(TangemSdkError("Command data signature not created. Cause: $errorMessage")); + sendError(PluginFlutterError("Command data signature not created. Cause: $errorMessage")); }); } catch (e) { - sendError(TangemSdkError("Can't create the command data: $e")); + sendError(PluginFlutterError("Can't create the command data: $e")); } } @@ -116,8 +116,8 @@ class TestBlock extends ActionBloc { } @override - sendError(TangemSdkBaseError? error) { - if (error is UserCancelledError) return; + sendError(TangemSdkPluginError? error) { + if (error?.isUserCancelledError() == true) return; super.sendError(error); } diff --git a/lib/app/ui/screen/card_action/test_screen.dart b/lib/app/ui/screen/card_action/test_screen.dart index 7871171..5deee6a 100644 --- a/lib/app/ui/screen/card_action/test_screen.dart +++ b/lib/app/ui/screen/card_action/test_screen.dart @@ -113,7 +113,7 @@ class _TestBodyState extends State { child: TextWidget.center("Error Response"), ), HorizontalDelimiter(), - StreamBuilder( + StreamBuilder( stream: _bloc.errorResponseStream, initialData: null, builder: (context, snapshot) { @@ -121,7 +121,7 @@ class _TestBodyState extends State { if (snapshot.data == null) return stub; final data = snapshot.data!; - if (data is TangemSdkError) { + if (data is TangemSdkPluginError) { Fluttertoast.showToast(msg: data.message, toastLength: Toast.LENGTH_LONG); return stub; } else { diff --git a/lib/app_test_assembler/domain/bloc/test_recorder_bloc.dart b/lib/app_test_assembler/domain/bloc/test_recorder_bloc.dart index 4f7251f..80cd209 100644 --- a/lib/app_test_assembler/domain/bloc/test_recorder_bloc.dart +++ b/lib/app_test_assembler/domain/bloc/test_recorder_bloc.dart @@ -3,7 +3,6 @@ import 'package:devkit/app_test_assembler/domain/model/json_test_model.dart'; import 'package:devkit/app_test_assembler/domain/test_storages.dart'; import 'package:devkit/commons/common_abstracts.dart'; import 'package:tangem_sdk/model/command_data.dart'; -import 'package:tangem_sdk/sdk_plugin.dart'; import 'package:tangem_sdk/tangem_sdk.dart'; class TestRecorderBlock extends BaseBloc { @@ -65,7 +64,7 @@ class TestRecorderBlock extends BaseBloc { _currentRecord = null; } - handleCommandError(TangemSdkBaseError? error) { + handleCommandError(TangemSdkPluginError? error) { _currentRecord = null; } } @@ -125,8 +124,9 @@ class TestAssembler { final jsonRpc = JSONRPCRequest.fromCommandDataJson(jsonData); final expectedResult = (record.response as TangemSdkResponse).toJson(); + final modelName = ["$index", jsonRpc.method].join("_"); return StepModel( - "$index.${stepConfig.name}.${jsonRpc.method}", + modelName, jsonRpc.method, jsonRpc.params, expectedResult, diff --git a/plugin/tangem-sdk-flutter/android/build.gradle b/plugin/tangem-sdk-flutter/android/build.gradle index a85387d..0902eac 100644 --- a/plugin/tangem-sdk-flutter/android/build.gradle +++ b/plugin/tangem-sdk-flutter/android/build.gradle @@ -53,8 +53,8 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation 'com.tangem.tangem-sdk-kotlin:core:develop-42' - implementation 'com.tangem.tangem-sdk-kotlin:android:develop-42' + implementation 'com.tangem.tangem-sdk-kotlin:core:develop-43' + implementation 'com.tangem.tangem-sdk-kotlin:android:develop-43' implementation "com.squareup.sqldelight:android-driver:1.4.0" diff --git a/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt b/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt index 16a4345..fc0104a 100644 --- a/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt +++ b/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt @@ -30,7 +30,6 @@ import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result -import java.lang.IllegalStateException import java.lang.ref.WeakReference import java.util.* @@ -420,17 +419,22 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { if (replyAlreadySubmit) return replyAlreadySubmit = true + val exception = ex as? PluginException ?: TangemSdkException(ex) handler.post { - val code = 9999 - val localizedDescription: String = ex.toString() - result.error("$code".capitalize(), localizedDescription, + val code = 1000 + val localizedDescription: String = exception.toString() + result.error("$code", localizedDescription, converter.toJson(PluginError(code, localizedDescription))) } } - @Throws(Exception::class) + @Throws(PluginException::class) inline fun MethodCall.extract(name: String): T { - return this.extractOptional(name) ?: throw NoSuchFieldException(name) + return try { + this.extractOptional(name) ?: throw PluginException("MethodCall.extract: no such field: $name, or field is NULL") + } catch (ex: Exception) { + throw ex as? PluginException ?: PluginException("MethodCall.extractOptional", ex) + } } inline fun MethodCall.extractOptional(name: String): T? { @@ -472,8 +476,8 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { private fun startSession(call: MethodCall, result: Result) { try { - if (cardSession != null && cardSession!!.state == CardSessionState.Active) - throw IllegalStateException("The CardSession has already started") + if (cardSession != null && cardSession !!.state == CardSessionState.Active) + throw PluginException("The CardSession has already started") sdk.startSession( call.extractOptional("cardId"), @@ -494,7 +498,7 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { private fun stopSession(call: MethodCall, result: Result) { try { - val session = cardSession ?: throw UnsupportedOperationException("Session not started") + val session = cardSession ?: throw PluginException("Session not started") session.stop() cardSession = null handleResult(result, CompletionResult.Success(true)) @@ -505,7 +509,7 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { private fun runJSONRPCRequest(call: MethodCall, result: Result) { try { - val session = cardSession ?: throw UnsupportedOperationException("Session not started") + val session = cardSession ?: throw PluginException("Session not started") val stringOfJSONRPCRequest = call.extract("JSONRPCRequest") session.run(stringOfJSONRPCRequest) { response -> @@ -513,7 +517,7 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { replyAlreadySubmit = true val jsonRpcResponse = converter.fromJson(response) - ?: throw IllegalArgumentException("Can't convert the string response to JSONRPCResponse") + ?: throw PluginException("Can't convert the string response to JSONRPCResponse") if (jsonRpcResponse.error == null) { handler.post { result.success(response) } @@ -572,4 +576,15 @@ class MoshiAdapters { } } -data class PluginError(val code: Int, val localizedDescription: String) \ No newline at end of file +data class PluginError( + // code = 1000 - it's the plugin or it's the tangemSdk internal exception + // any other value in code greater than 10000 - it's the tangemSdk internal error + val code: Int, + val localizedDescription: String +) + +class PluginException( + message: String, cause: Throwable? = null +): Exception("TangemSdkPlugin exception. Message: $message", cause) + +class TangemSdkException(cause: Throwable): Exception("TangemSdk internal exception", cause) diff --git a/plugin/tangem-sdk-flutter/example/lib/main.dart b/plugin/tangem-sdk-flutter/example/lib/main.dart index f35faae..20e7b86 100644 --- a/plugin/tangem-sdk-flutter/example/lib/main.dart +++ b/plugin/tangem-sdk-flutter/example/lib/main.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; +import 'package:tangem_sdk/plugin_error.dart'; import 'package:tangem_sdk/tangem_sdk.dart'; import 'app_widgets.dart'; @@ -46,7 +47,7 @@ class _CommandListWidgetState extends State { final prettyJson = _jsonEncoder.convert(success.toJson()); prettyJson.split("\n").forEach((element) => print(element)); }, (error) { - if (error is SdkPluginError) { + if (error is TangemSdkPluginError) { print(error.message); } else { print(error); diff --git a/plugin/tangem-sdk-flutter/lib/plugin_error.dart b/plugin/tangem-sdk-flutter/lib/plugin_error.dart new file mode 100644 index 0000000..2697267 --- /dev/null +++ b/plugin/tangem-sdk-flutter/lib/plugin_error.dart @@ -0,0 +1,65 @@ +import 'dart:convert'; + +import 'package:flutter/services.dart'; + +abstract class TangemSdkPluginError implements Exception { + final int code; + final String message; + + TangemSdkPluginError(this.code, this.message); + + String toString() => "${this.runtimeType}. Code: $code, message: $message"; + + static const int unknownCode = -1; + static const int pluginFlutter = 100; + static const int pluginKotlin = 1000; + + static TangemSdkPluginError createError(dynamic error) { + if (error is TangemSdkPluginError) return error; + + if (error is PlatformException) { + final code = int.tryParse(error.code) ?? unknownCode; + final message = error.message ?? error.details ?? ""; + + switch (code) { + case unknownCode: + return PluginUnknownError(message, error.details?.toString() ?? ""); + case pluginKotlin: + return PluginKotlinError(message); + default: + return PluginTangemSdkError(code, message); + } + } + return PluginUnknownError("", jsonEncode(error)); + } +} + +class PluginFlutterError extends TangemSdkPluginError { + PluginFlutterError(String message) : super(TangemSdkPluginError.pluginFlutter, message); +} + +class PluginKotlinError extends TangemSdkPluginError { + PluginKotlinError(String message) : super(TangemSdkPluginError.pluginKotlin, message); +} + +class PluginTangemSdkError extends TangemSdkPluginError { + PluginTangemSdkError(int code, String message) : super(code, message); +} + +class PluginUnknownError extends TangemSdkPluginError { + final String details; + + PluginUnknownError(String message, this.details) : super(TangemSdkPluginError.unknownCode, message); +} + +extension OnTangemSdkPluginError on TangemSdkPluginError { + bool isUnknownError() => code == TangemSdkPluginError.unknownCode; + + bool isPluginFlutterError() => code == TangemSdkPluginError.pluginFlutter; + + bool isPluginKotlinError() => code == TangemSdkPluginError.pluginKotlin; + + bool isTangemSdkError() => code >= 10000 && code <= 100000; + + bool isUserCancelledError() => code == 50002; +} diff --git a/plugin/tangem-sdk-flutter/lib/sdk_plugin.dart b/plugin/tangem-sdk-flutter/lib/sdk_plugin.dart index 85f7351..658bfc5 100644 --- a/plugin/tangem-sdk-flutter/lib/sdk_plugin.dart +++ b/plugin/tangem-sdk-flutter/lib/sdk_plugin.dart @@ -5,6 +5,7 @@ import 'package:flutter/services.dart'; import 'package:tangem_sdk/card_responses/card_response.dart'; import 'package:tangem_sdk/model/command_data.dart'; import 'package:tangem_sdk/model/json_rpc.dart'; +import 'package:tangem_sdk/plugin_error.dart'; import 'model/sdk.dart'; @@ -45,6 +46,7 @@ class TangemSdk { static const initialMessageBody = "body"; static const hashes = "hashes"; static const walletPublicKey = "walletPublicKey"; + //TODO: replace by walletPublicKey @Deprecated("replace by walletPublicKey") static const walletIndex = "walletIndex"; @@ -130,13 +132,13 @@ class TangemSdk { jsonMap = map; } } catch (exception) { - _sendBackError(callback, TangemSdkError("Can't get command json data. Error: ${exception.toString()}")); + _sendBackError(callback, PluginFlutterError("Can't get command json data. Error: ${exception.toString()}")); return; } final type = jsonMap[commandType]; if (type == null) { - _sendBackError(callback, TangemSdkError("Can't execute the task. Missing the '$commandType' field")); + _sendBackError(callback, PluginFlutterError("Can't execute the task. Missing the '$commandType' field")); return; } onPrepareComplete(jsonMap); @@ -350,27 +352,10 @@ class TangemSdk { } static _sendBackError(Callback callback, dynamic error) { - if (error is TangemSdkBaseError) { - callback.onError(error); - } else if (error is PlatformException) { - final jsonString = error.details; - final map = json.decode(jsonString); - if (map["code"] == 50002) { - callback.onError(UserCancelledError(map['localizedDescription'])); - } else if (map["jsonrpc"] != null) { - callback.onError(SdkPluginError(jsonString)); - }else { - callback.onError(SdkPluginError(map['localizedDescription'])); - } - } else if (error is Exception) { - callback.onError(SdkPluginError(error.toString())); - } else { - callback.onError(SdkPluginError("Unknown plugin error: ${error.toString()}")); - } + callback.onError(TangemSdkPluginError.createError(error)); } - - static Future runJSONRPCRequest(Callback callback, JSONRPCRequest request) async{ + static Future runJSONRPCRequest(Callback callback, JSONRPCRequest request) async { final mapWithRequest = {"JSONRPCRequest": jsonEncode(request.toJson())}; _channelJSONRPC .invokeMethod(cJsonRpcRequest, mapWithRequest) @@ -384,36 +369,9 @@ class TangemSdk { } } -abstract class TangemSdkBaseError implements Exception { - final String message; - - TangemSdkBaseError(this.message); - - String toString() => "${this.runtimeType}: $message"; -} - -class SdkPluginError extends TangemSdkBaseError { - SdkPluginError(String message) : super(message); -} - -class TangemSdkError extends TangemSdkBaseError { - TangemSdkError(String message) : super(message); -} - -class UserCancelledError extends SdkPluginError { - UserCancelledError(String message) : super(message); -} - class Callback { final Function(dynamic success) onSuccess; final Function(dynamic error) onError; Callback(this.onSuccess, this.onError); -} - -class TangemSdkJson { - static const keyMethod = "method"; - static const keyParams = "parameters"; - - static const methodScan = "SCAN_TASK"; -} +} \ No newline at end of file diff --git a/plugin/tangem-sdk-flutter/lib/tangem_sdk.dart b/plugin/tangem-sdk-flutter/lib/tangem_sdk.dart index f091826..39d9bed 100644 --- a/plugin/tangem-sdk-flutter/lib/tangem_sdk.dart +++ b/plugin/tangem-sdk-flutter/lib/tangem_sdk.dart @@ -7,4 +7,5 @@ export 'model/masks/settings_mask.dart'; export 'model/masks/signing_method_mask.dart'; export 'model/masks/wallet_settings_mask.dart'; export 'model/sdk.dart'; +export 'plugin_error.dart'; export 'sdk_plugin.dart'; From 9092afdc955ec9b89ca13752f487b88a3e16a58f Mon Sep 17 00:00:00 2001 From: Anton Zhilenkov Date: Mon, 21 Jun 2021 17:21:15 +0300 Subject: [PATCH 05/12] AND-1121 added check for the expected and actual result --- .../domain/error/test_assert_error.dart | 9 ++++++ .../domain/executable/step/step_launcher.dart | 30 +++++++++++++++++++ .../domain/variable_service.dart | 4 +-- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/lib/app_test_launcher/domain/error/test_assert_error.dart b/lib/app_test_launcher/domain/error/test_assert_error.dart index 105598b..5250e1a 100644 --- a/lib/app_test_launcher/domain/error/test_assert_error.dart +++ b/lib/app_test_launcher/domain/error/test_assert_error.dart @@ -2,6 +2,15 @@ import 'error.dart'; abstract class TestAssertError implements TestFrameworkError {} +class ExpectedAndActualResultError extends TestAssertError { + final String? message; + + ExpectedAndActualResultError([this.message]); + + @override + String get errorMessage => "Expected and actual results doesn't match. $message"; +} + class EqualsError extends TestAssertError { final dynamic firstValue; final dynamic secondValue; diff --git a/lib/app_test_launcher/domain/executable/step/step_launcher.dart b/lib/app_test_launcher/domain/executable/step/step_launcher.dart index e8d6556..91ee777 100644 --- a/lib/app_test_launcher/domain/executable/step/step_launcher.dart +++ b/lib/app_test_launcher/domain/executable/step/step_launcher.dart @@ -5,6 +5,7 @@ import 'package:devkit/app_test_assembler/domain/model/json_test_model.dart'; import 'package:devkit/app_test_launcher/domain/common/test_result.dart'; import 'package:devkit/app_test_launcher/domain/common/typedefs.dart'; import 'package:devkit/app_test_launcher/domain/error/error.dart'; +import 'package:devkit/app_test_launcher/domain/error/test_assert_error.dart'; import 'package:devkit/app_test_launcher/domain/error/test_executable_error.dart'; import 'package:devkit/app_test_launcher/domain/executable/assert/assert.dart'; import 'package:devkit/app_test_launcher/domain/executable/assert/assert_launcher.dart'; @@ -25,6 +26,11 @@ class StepLauncher implements Executable { _fetchVariables(); final jsonRpcRequestCallback = Callback((result) { + final error = _checkExpectedWithActualResult(_model.expectedResult, result); + if (error != null) { + callback(Failure(error)); + return; + } VariableService.registerResult(_model.name, result); _executeAsserts(callback); }, (error) { @@ -64,4 +70,28 @@ class StepLauncher implements Executable { AssertsLauncher(assertsQueue).run(callback); } + + TestAssertError? _checkExpectedWithActualResult(Map expResult, dynamic actualResult) { + if (actualResult is! Map) return ExpectedAndActualResultError("Actual result is not a map"); + + final jsonRpcResult = actualResult as Map; + final jsonRpcResponse = JSONRPCResponse.fromJson(jsonRpcResult); + if (jsonRpcResponse.result is! Map) + return ExpectedAndActualResultError("JsonRpc result is not a map"); + + final result = jsonRpcResponse.result as Map; + + if (expResult.length != result.length) return ExpectedAndActualResultError("Results length doesn't match"); + + final missedKeys = expResult.keys.toList()..removeWhere((element) => result.containsKey(element)); + final unexpectedKeys = result.keys.toList()..removeWhere((element) => expResult.containsKey(element)); + + if (missedKeys.isNotEmpty || unexpectedKeys.isNotEmpty) { + final missed = "Missed keys in the actual result: [${missedKeys.join(", ")}]"; + final unexpected = "Unexpected keys in the actual result: [${unexpectedKeys.join(", ")}]"; + return ExpectedAndActualResultError("$missed. $unexpected"); + } + + return null; + } } diff --git a/lib/app_test_launcher/domain/variable_service.dart b/lib/app_test_launcher/domain/variable_service.dart index 0f0c54c..06bffb5 100644 --- a/lib/app_test_launcher/domain/variable_service.dart +++ b/lib/app_test_launcher/domain/variable_service.dart @@ -9,7 +9,7 @@ class VariableService { static final _bracketRight = "}"; static final _stepPointer = "#"; static final _parent = "#parent"; - static final _result = "result"; + static final _actualResult = "actualResult"; static final _stepValues = {}; @@ -24,7 +24,7 @@ class VariableService { return; } - stepMap[_result] = response.result; + stepMap[_actualResult] = response.result; } static void registerStep(String name, SourceMap source) { From eeca56845e3529c00ae0d917fc3b0aa4af87f852 Mon Sep 17 00:00:00 2001 From: Anton Zhilenkov Date: Mon, 21 Jun 2021 17:33:35 +0300 Subject: [PATCH 06/12] AND-1135 repackage --- .../domain/repository/repositories.dart | 36 +++++ .../domain/test_launcher.dart | 114 ++++++++++++++ .../ui/screen/json_test_launcher_screen.dart | 143 +----------------- 3 files changed, 152 insertions(+), 141 deletions(-) create mode 100644 lib/app_test_launcher/domain/repository/repositories.dart create mode 100644 lib/app_test_launcher/domain/test_launcher.dart diff --git a/lib/app_test_launcher/domain/repository/repositories.dart b/lib/app_test_launcher/domain/repository/repositories.dart new file mode 100644 index 0000000..edcf3f1 --- /dev/null +++ b/lib/app_test_launcher/domain/repository/repositories.dart @@ -0,0 +1,36 @@ +import 'dart:async'; + +import 'package:devkit/app_test_assembler/domain/model/json_test_model.dart'; +import 'package:devkit/app_test_assembler/domain/test_storages.dart'; +import 'package:tangem_sdk/extensions/exp_extensions.dart'; + +abstract class JsonTestsRepository { + Future init(); + + List getList(); + + JsonTest? get(String name); +} + +class JsonTestRepository implements JsonTestsRepository { + final JsonTestsStorage storage = JsonTestsStorage(); + + @override + Future init() { + final completer = Completer(); + storage.isReadyToUseStream.listen((event) { + if (event) completer.complete(); + }); + return completer.future; + } + + @override + JsonTest? get(String name) { + return storage.get(name); + } + + @override + List getList() { + return storage.names().map((e) => storage.get(e)).toList().toNullSafe(); + } +} diff --git a/lib/app_test_launcher/domain/test_launcher.dart b/lib/app_test_launcher/domain/test_launcher.dart new file mode 100644 index 0000000..0c5018c --- /dev/null +++ b/lib/app_test_launcher/domain/test_launcher.dart @@ -0,0 +1,114 @@ +import 'dart:async'; +import 'dart:collection'; +import 'dart:convert'; + +import 'package:devkit/app_test_assembler/domain/model/json_test_model.dart'; +import 'package:tangem_sdk/extensions/exp_extensions.dart'; + +import 'common/test_result.dart'; +import 'common/typedefs.dart'; +import 'error/error.dart'; +import 'error/test_error.dart'; +import 'executable/assert/assert.dart'; +import 'executable/step/step_launcher.dart'; +import 'variable_service.dart'; + +class TestLauncher { + final JsonTest _jsonTest; + final AssertsFactory _assertsFactory; + + OnComplete? onTestComplete; + + Queue _testQueue = Queue(); + Queue _stepQueue = Queue(); + + TestLauncher(this._jsonTest, this._assertsFactory); + + void launch() { + _testQueue = _generateTestQueue(); + + final nextTest = _testQueue.poll(); + if (nextTest == null) { + onTestComplete?.call(Failure(TestIsEmptyError())); + } else { + _runTest(nextTest); + } + } + + void _runTest(JsonTest test) { + VariableService.reset(); + _prepare().then((value) { + _stepQueue = Queue.from(test.steps); + final nextStep = _stepQueue.poll(); + if (nextStep == null) { + _onStepSequenceComplete(Success(test.setup.name)); + } else { + _runStep(nextStep); + } + }).onError((error, stackTrace) { + _handleError(error); + }); + } + + void _onStepComplete(TestResult result) { + if (result is Success) { + final nextStep = _stepQueue.poll(); + if (nextStep == null) { + _onStepSequenceComplete(result); + } else { + _runStep(nextStep); + } + } else { + _onStepSequenceComplete(result); + } + } + + void _runStep(StepModel step) { + StepLauncher(step, _assertsFactory).run(_onStepComplete); + } + + void _onStepSequenceComplete(TestResult result) { + if (result is Success) { + final nextTest = _testQueue.poll(); + if (nextTest == null) { + onTestComplete?.call(Success(_jsonTest.setup.name)); + } else { + _runTest(nextTest); + } + } else { + _handleError(result); + } + } + + void _handleError(dynamic error) { + onTestComplete?.call(Failure(CustomError(jsonEncode(error)))); + } + + Queue _generateTestQueue() { + final tests = []; + if (_jsonTest.setup.iterations == null) { + tests.add(_jsonTest); + } else { + _jsonTest.setup.iterations?.foreach((e) => tests.add(_jsonTest)); + } + return Queue.from(tests); + } + + Future _prepare() { + final completer = Completer(); + _configureSdk(() { + _rePersonalize(() { + completer.complete(); + }, _handleError); + }, _handleError); + return completer.future; + } + + void _configureSdk(Function onSuccess, Function(dynamic error) onError) { + onSuccess(); + } + + void _rePersonalize(Function onSuccess, Function(dynamic error) onError) { + onSuccess(); + } +} diff --git a/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart b/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart index c4fc2a2..9cb373c 100644 --- a/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart +++ b/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart @@ -1,18 +1,10 @@ -import 'dart:async'; -import 'dart:collection'; -import 'dart:convert'; - import 'package:devkit/app/ui/screen/card_action/helpers.dart'; import 'package:devkit/app/ui/widgets/app_widgets.dart'; import 'package:devkit/app_test_assembler/domain/model/json_test_model.dart'; -import 'package:devkit/app_test_assembler/domain/test_storages.dart'; import 'package:devkit/app_test_launcher/domain/common/test_result.dart'; -import 'package:devkit/app_test_launcher/domain/common/typedefs.dart'; -import 'package:devkit/app_test_launcher/domain/error/error.dart'; -import 'package:devkit/app_test_launcher/domain/error/test_error.dart'; import 'package:devkit/app_test_launcher/domain/executable/assert/assert.dart'; -import 'package:devkit/app_test_launcher/domain/executable/step/step_launcher.dart'; -import 'package:devkit/app_test_launcher/domain/variable_service.dart'; +import 'package:devkit/app_test_launcher/domain/repository/repositories.dart'; +import 'package:devkit/app_test_launcher/domain/test_launcher.dart'; import 'package:devkit/commons/common_abstracts.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -154,134 +146,3 @@ class JsonTestLauncherBloc extends BaseBloc { sendSnackbarMessage(error); } } - -class TestLauncher { - final JsonTest _jsonTest; - final AssertsFactory _assertsFactory; - - OnComplete? onTestComplete; - - Queue _testQueue = Queue(); - Queue _stepQueue = Queue(); - - TestLauncher(this._jsonTest, this._assertsFactory); - - void launch() { - _testQueue = _generateTestQueue(); - - final nextTest = _testQueue.poll(); - if (nextTest == null) { - onTestComplete?.call(Failure(TestIsEmptyError())); - } else { - _runTest(nextTest); - } - } - - void _runTest(JsonTest test) { - VariableService.reset(); - _prepare().then((value) { - _stepQueue = Queue.from(test.steps); - final nextStep = _stepQueue.poll(); - if (nextStep == null) { - _onStepSequenceComplete(Success(test.setup.name)); - } else { - _runStep(nextStep); - } - }).onError((error, stackTrace) { - _handleError(error); - }); - } - - void _onStepComplete(TestResult result) { - if (result is Success) { - final nextStep = _stepQueue.poll(); - if (nextStep == null) { - _onStepSequenceComplete(result); - } else { - _runStep(nextStep); - } - } else { - _onStepSequenceComplete(result); - } - } - - void _runStep(StepModel step) { - StepLauncher(step, _assertsFactory).run(_onStepComplete); - } - - void _onStepSequenceComplete(TestResult result) { - if (result is Success) { - final nextTest = _testQueue.poll(); - if (nextTest == null) { - onTestComplete?.call(Success(_jsonTest.setup.name)); - } else { - _runTest(nextTest); - } - } else { - _handleError(result); - } - } - - void _handleError(dynamic error) { - onTestComplete?.call(Failure(CustomError(jsonEncode(error)))); - } - - Queue _generateTestQueue() { - final tests = []; - if (_jsonTest.setup.iterations == null) { - tests.add(_jsonTest); - } else { - _jsonTest.setup.iterations?.foreach((e) => tests.add(_jsonTest)); - } - return Queue.from(tests); - } - - Future _prepare() { - final completer = Completer(); - _configureSdk(() { - _rePersonalize(() { - completer.complete(); - }, _handleError); - }, _handleError); - return completer.future; - } - - void _configureSdk(Function onSuccess, Function(dynamic error) onError) { - onSuccess(); - } - - void _rePersonalize(Function onSuccess, Function(dynamic error) onError) { - onSuccess(); - } -} - -abstract class JsonTestsRepository { - Future init(); - - List getList(); - - JsonTest? get(String name); -} - -class JsonTestRepository implements JsonTestsRepository { - final JsonTestsStorage storage = JsonTestsStorage(); - - @override - Future init() { - final completer = Completer(); - storage.isReadyToUseStream.listen((event) { - if (event) completer.complete(); - }); - return completer.future; - } - - @override - JsonTest? get(String name) { - return storage.get(name); - } - - @override - List getList() { - return storage.names().map((e) => storage.get(e)).toList().toNullSafe(); - } -} From 18ff8d3664e4ebd9eb514cc75ed5936273524de4 Mon Sep 17 00:00:00 2001 From: Anton Zhilenkov Date: Tue, 22 Jun 2021 14:36:58 +0300 Subject: [PATCH 07/12] AND-1136 sends a cardId and an initial message from the setup config to the starting session --- lib/app/domain/model/command_data_models.dart | 4 +-- .../domain/model/json_test_model.dart | 20 ++++------- .../domain/model/json_test_model.g.dart | 8 +---- .../domain/test_launcher.dart | 35 ++++++++++++++----- .../ui/screen/json_test_launcher_screen.dart | 35 +++++-------------- .../tangem-sdk-flutter/android/build.gradle | 8 +++-- .../plugin/tangem_sdk/TangemSdkPlugin.kt | 20 ++++++++--- .../tangem-sdk-flutter/example/lib/main.dart | 24 ++++++------- .../lib/model/command_data.dart | 4 +-- plugin/tangem-sdk-flutter/lib/sdk_plugin.dart | 6 ++-- 10 files changed, 83 insertions(+), 81 deletions(-) diff --git a/lib/app/domain/model/command_data_models.dart b/lib/app/domain/model/command_data_models.dart index 29abf42..3385185 100644 --- a/lib/app/domain/model/command_data_models.dart +++ b/lib/app/domain/model/command_data_models.dart @@ -110,7 +110,7 @@ class WriteIssuerDataModel extends CommandDataModel { factory WriteIssuerDataModel.fromJson(Map json) { final model = WriteIssuerDataModel( - json[TangemSdk.cid], + json[TangemSdk.cardId], json[TangemSdk.issuerData], json[TangemSdk.privateKey], json[TangemSdk.issuerDataCounter], @@ -175,7 +175,7 @@ class WriteIssuerExDataModel extends CommandDataModel { factory WriteIssuerExDataModel.fromJson(Map json) { final model = WriteIssuerExDataModel( - json[TangemSdk.cid], + json[TangemSdk.cardId], json[TangemSdk.issuerData], json[TangemSdk.privateKey], json[TangemSdk.issuerDataCounter], diff --git a/lib/app_test_assembler/domain/model/json_test_model.dart b/lib/app_test_assembler/domain/model/json_test_model.dart index bbbaf7e..cdfa78f 100644 --- a/lib/app_test_assembler/domain/model/json_test_model.dart +++ b/lib/app_test_assembler/domain/model/json_test_model.dart @@ -27,7 +27,7 @@ class TestSetup { final String name; final String description; final Map personalizationConfig; - final ConfigSdk? sdkConfig; + final Map sdkConfig; final FirmwareVersion? minimalFirmware; final String? platform; final int? iterations; @@ -36,8 +36,8 @@ class TestSetup { TestSetup( this.name, this.description, - this.personalizationConfig, [ - this.sdkConfig, + this.personalizationConfig, + this.sdkConfig, [ this.minimalFirmware, this.platform, this.iterations, @@ -51,7 +51,7 @@ class TestSetup { "Simple test", "It shows what we can do with a card", persCommandConfig, - null, + {}, null, null, 1, @@ -66,7 +66,7 @@ class TestSetup { String? name, String? description, Map? personalizationConfig, - ConfigSdk? sdkConfig, + Map? sdkConfig, FirmwareVersion? minimalFirmware, String? platform, int? iterations, @@ -82,15 +82,6 @@ class TestSetup { ); } -@JsonSerializable() -class ConfigSdk { - ConfigSdk(); - - factory ConfigSdk.fromJson(Map json) => ConfigSdk(); - - Map toJson() => {}; -} - @JsonSerializable() class StepModel { final String name; @@ -102,6 +93,7 @@ class StepModel { final int? iterations; Map _rawParams = {}; + Map get rawParams => {}..addAll(_rawParams); StepModel( diff --git a/lib/app_test_assembler/domain/model/json_test_model.g.dart b/lib/app_test_assembler/domain/model/json_test_model.g.dart index 78db61e..f8f797f 100644 --- a/lib/app_test_assembler/domain/model/json_test_model.g.dart +++ b/lib/app_test_assembler/domain/model/json_test_model.g.dart @@ -23,7 +23,7 @@ TestSetup _$TestSetupFromJson(Map json) { json['name'] as String, json['description'] as String, json['personalizationConfig'] as Map, - json['sdkConfig'] == null ? null : ConfigSdk.fromJson(json['sdkConfig'] as Map), + json['sdkConfig'] as Map, json['minimalFirmware'] == null ? null : FirmwareVersion.fromJson(json['minimalFirmware'] as String), json['platform'] as String?, json['iterations'] as int?, @@ -42,12 +42,6 @@ Map _$TestSetupToJson(TestSetup instance) => { 'creationDateMs': instance.creationDateMs, }; -ConfigSdk _$ConfigSdkFromJson(Map json) { - return ConfigSdk(); -} - -Map _$ConfigSdkToJson(ConfigSdk instance) => {}; - StepModel _$TestStepFromJson(Map json) { return StepModel( json['name'] as String, diff --git a/lib/app_test_launcher/domain/test_launcher.dart b/lib/app_test_launcher/domain/test_launcher.dart index 0c5018c..e547edb 100644 --- a/lib/app_test_launcher/domain/test_launcher.dart +++ b/lib/app_test_launcher/domain/test_launcher.dart @@ -4,6 +4,7 @@ import 'dart:convert'; import 'package:devkit/app_test_assembler/domain/model/json_test_model.dart'; import 'package:tangem_sdk/extensions/exp_extensions.dart'; +import 'package:tangem_sdk/tangem_sdk.dart'; import 'common/test_result.dart'; import 'common/typedefs.dart'; @@ -25,14 +26,28 @@ class TestLauncher { TestLauncher(this._jsonTest, this._assertsFactory); void launch() { - _testQueue = _generateTestQueue(); + _startSession(() { + _testQueue = _generateTestQueue(); + final nextTest = _testQueue.poll(); + if (nextTest == null) { + onTestComplete?.call(Failure(TestIsEmptyError())); + } else { + _runTest(nextTest); + } + }, (startingSessionError) { + onTestComplete?.call(Failure(startingSessionError)); + }); + } - final nextTest = _testQueue.poll(); - if (nextTest == null) { - onTestComplete?.call(Failure(TestIsEmptyError())); - } else { - _runTest(nextTest); - } + void _startSession(Function onSuccess, Function(dynamic error) onError) { + TangemSdk.startSession(Callback((success) => onSuccess(), onError), { + TangemSdk.initialMessage: _jsonTest.setup.sdkConfig[TangemSdk.initialMessage], + TangemSdk.cardId: _jsonTest.setup.sdkConfig[TangemSdk.cardId], + }); + } + + void _stopSession(Function onSuccess, Function(dynamic error) onError) { + TangemSdk.stopSession(Callback((success) => onSuccess(), (error) => onError(error))); } void _runTest(JsonTest test) { @@ -71,7 +86,11 @@ class TestLauncher { if (result is Success) { final nextTest = _testQueue.poll(); if (nextTest == null) { - onTestComplete?.call(Success(_jsonTest.setup.name)); + _stopSession((){ + onTestComplete?.call(Success(_jsonTest.setup.name)); + }, (error) { + _handleError(error); + }); } else { _runTest(nextTest); } diff --git a/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart b/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart index 9cb373c..df299be 100644 --- a/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart +++ b/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart @@ -10,7 +10,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:rxdart/rxdart.dart'; import 'package:tangem_sdk/extensions/exp_extensions.dart'; -import 'package:tangem_sdk/sdk_plugin.dart'; class JsonTestLauncherScreen extends StatefulWidget { const JsonTestLauncherScreen({Key? key}) : super(key: key); @@ -114,32 +113,16 @@ class JsonTestLauncherBloc extends BaseBloc { } void launch(JsonTest jsonTest) { - _startSession(() async { - final launcher = TestLauncher(jsonTest, AssertsFactory()); - launcher.onTestComplete = (result) { - if (result is Success) { - final message = result.name ?? "Unknown test"; - sendSnackbarMessage("$message is completed"); - } else { - sendSnackbarMessage(result); - } - _stopSession(() {}, _handleError); - }; - launcher.launch(); - }, _handleError); - } - - void _startSession(Function onSuccess, Function(dynamic error) onError) { - TangemSdk.startSession(Callback((success) => onSuccess(), onError), { - TangemSdk.initialMessage: { - TangemSdk.initialMessageHeader: "Session is started", - TangemSdk.initialMessageBody: "COME ON !!!" + final launcher = TestLauncher(jsonTest, AssertsFactory()); + launcher.onTestComplete = (result) { + if (result is Success) { + final message = result.name ?? "Unknown test"; + sendSnackbarMessage("$message is completed"); + } else { + sendSnackbarMessage(result); } - }); - } - - void _stopSession(Function onSuccess, Function(dynamic error) onError) { - TangemSdk.stopSession(Callback((success) => onSuccess(), _handleError)); + }; + launcher.launch(); } void _handleError(dynamic error) { diff --git a/plugin/tangem-sdk-flutter/android/build.gradle b/plugin/tangem-sdk-flutter/android/build.gradle index 0902eac..4934c79 100644 --- a/plugin/tangem-sdk-flutter/android/build.gradle +++ b/plugin/tangem-sdk-flutter/android/build.gradle @@ -53,8 +53,12 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation 'com.tangem.tangem-sdk-kotlin:core:develop-43' - implementation 'com.tangem.tangem-sdk-kotlin:android:develop-43' +// implementation 'com.tangem.tangem-sdk-kotlin:core:develop-43' +// implementation 'com.tangem.tangem-sdk-kotlin:android:develop-43' + + // local dependencies + implementation 'com.tangem.tangem-sdk-kotlin:core:0.0.1' + implementation 'com.tangem.tangem-sdk-kotlin:android:0.0.1' implementation "com.squareup.sqldelight:android-driver:1.4.0" diff --git a/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt b/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt index fc0104a..4143c9d 100644 --- a/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt +++ b/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt @@ -509,13 +509,12 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { private fun runJSONRPCRequest(call: MethodCall, result: Result) { try { - val session = cardSession ?: throw PluginException("Session not started") - val stringOfJSONRPCRequest = call.extract("JSONRPCRequest") - session.run(stringOfJSONRPCRequest) { response -> - if (replyAlreadySubmit) return@run - replyAlreadySubmit = true + val callback = callbackWithResult@{ response: String -> + if (replyAlreadySubmit) return@callbackWithResult + + replyAlreadySubmit = true val jsonRpcResponse = converter.fromJson(response) ?: throw PluginException("Can't convert the string response to JSONRPCResponse") @@ -526,6 +525,17 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { handler.post { result.error("${error.code}", error.message, response) } } } + + if (cardSession == null) { + sdk.startSessionWithJsonRequest( + stringOfJSONRPCRequest, + call.extractOptional("cardId"), + call.extractOptional("initialMessage"), + callback + ) + } else { + cardSession !!.run(stringOfJSONRPCRequest, callback) + } } catch (ex: Exception) { handleException(result, ex) } diff --git a/plugin/tangem-sdk-flutter/example/lib/main.dart b/plugin/tangem-sdk-flutter/example/lib/main.dart index 20e7b86..3081c36 100644 --- a/plugin/tangem-sdk-flutter/example/lib/main.dart +++ b/plugin/tangem-sdk-flutter/example/lib/main.dart @@ -122,11 +122,11 @@ class _CommandListWidgetState extends State { final listOfData = List.generate(_utils.randomInt(1, 10), (index) => _utils.randomString(20)); final hashes = listOfData.map((e) => e.toHexString()).toList(); - TangemSdk.sign(_callback, hashes, {TangemSdk.cid: _cardId}); + TangemSdk.sign(_callback, hashes, {TangemSdk.cardId: _cardId}); } handleReadIssuerData() { - TangemSdk.readIssuerData(_callback, {TangemSdk.cid: _cardId}); + TangemSdk.readIssuerData(_callback, {TangemSdk.cardId: _cardId}); } handleWriteIssuerData() { @@ -140,13 +140,13 @@ class _CommandListWidgetState extends State { final issuerDataCounter = 1; TangemSdk.writeIssuerData(_callback, issuerData.toHexString(), issuerDataSignature.toHexString(), { - TangemSdk.cid: _cardId, + TangemSdk.cardId: _cardId, TangemSdk.issuerDataCounter: issuerDataCounter, }); } handleReadIssuerExtraData() { - TangemSdk.readIssuerExtraData(_callback, {TangemSdk.cid: _cardId}); + TangemSdk.readIssuerExtraData(_callback, {TangemSdk.cardId: _cardId}); } handleWriteIssuerExtraData() { @@ -163,13 +163,13 @@ class _CommandListWidgetState extends State { TangemSdk.writeIssuerExtraData( _callback, issuerData.toHexString(), startingSignature.toHexString(), finalizingSignature.toHexString(), { - TangemSdk.cid: _cardId, + TangemSdk.cardId: _cardId, TangemSdk.issuerDataCounter: counter, }); } handleReadUserData() { - TangemSdk.readUserData(_callback, {TangemSdk.cid: _cardId}); + TangemSdk.readUserData(_callback, {TangemSdk.cardId: _cardId}); } handleWriteUserData() { @@ -177,7 +177,7 @@ class _CommandListWidgetState extends State { final userCounter = 1; TangemSdk.writeUserData(_callback, userData.toHexString(), { - TangemSdk.cid: _cardId, + TangemSdk.cardId: _cardId, TangemSdk.userCounter: userCounter, }); } @@ -187,25 +187,25 @@ class _CommandListWidgetState extends State { final protectedCounter = 1; TangemSdk.writeUserProtectedData(_callback, userProtectedData.toHexString(), { - TangemSdk.cid: _cardId, + TangemSdk.cardId: _cardId, TangemSdk.userProtectedCounter: protectedCounter, }); } handleCreateWallet() { - TangemSdk.createWallet(_callback, {TangemSdk.cid: _cardId}); + TangemSdk.createWallet(_callback, {TangemSdk.cardId: _cardId}); } handlePurgeWallet() { - TangemSdk.purgeWallet(_callback, {TangemSdk.cid: _cardId}); + TangemSdk.purgeWallet(_callback, {TangemSdk.cardId: _cardId}); } handleSetPin1() { - TangemSdk.setPinCode(_callback, PinType.PIN1, {TangemSdk.cid: _cardId}); + TangemSdk.setPinCode(_callback, PinType.PIN1, {TangemSdk.cardId: _cardId}); } handleSetPin2() { - TangemSdk.setPinCode(_callback, PinType.PIN2, {TangemSdk.cid: _cardId}); + TangemSdk.setPinCode(_callback, PinType.PIN2, {TangemSdk.cardId: _cardId}); } _showToast(String message) { diff --git a/plugin/tangem-sdk-flutter/lib/model/command_data.dart b/plugin/tangem-sdk-flutter/lib/model/command_data.dart index 1841f93..b30846c 100644 --- a/plugin/tangem-sdk-flutter/lib/model/command_data.dart +++ b/plugin/tangem-sdk-flutter/lib/model/command_data.dart @@ -25,13 +25,13 @@ abstract class CommandDataModel { Map getBaseData() { final map = {TangemSdk.commandType: type}; - if (cardId != null) map[TangemSdk.cid] = cardId; + if (cardId != null) map[TangemSdk.cardId] = cardId; if (initialMessage != null) map[TangemSdk.initialMessage] = initialMessage!.toJson(); return map; } static T attachBaseData(T taskData, Map json) { - taskData.cardId = json[TangemSdk.cid]; + taskData.cardId = json[TangemSdk.cardId]; if (json[TangemSdk.initialMessage] != null) taskData.initialMessage = Message.fromJson(json[TangemSdk.initialMessage]); return taskData; diff --git a/plugin/tangem-sdk-flutter/lib/sdk_plugin.dart b/plugin/tangem-sdk-flutter/lib/sdk_plugin.dart index 658bfc5..1091b71 100644 --- a/plugin/tangem-sdk-flutter/lib/sdk_plugin.dart +++ b/plugin/tangem-sdk-flutter/lib/sdk_plugin.dart @@ -40,7 +40,7 @@ class TangemSdk { static const cJsonRpcRequest = 'JSONRPCRequest'; static const isAllowedOnlyDebugCards = "isAllowedOnlyDebugCards"; - static const cid = "cardId"; + static const cardId = "cardId"; static const initialMessage = "initialMessage"; static const initialMessageHeader = "header"; static const initialMessageBody = "body"; @@ -289,10 +289,10 @@ class TangemSdk { .catchError((error) => _sendBackError(callback, error)); } - static Future prepareHashes(Callback callback, String cardId, String fileDataHex, int counter, + static Future prepareHashes(Callback callback, String cid, String fileDataHex, int counter, [String? privateKeyHex]) async { final valuesToExport = { - cid: cardId, + cid: cid, fileData: fileDataHex, fileCounter: counter, privateKey: privateKeyHex, From c03de91b32ec25d9777c55cd500d1ec5da66a405 Mon Sep 17 00:00:00 2001 From: Anton Zhilenkov Date: Wed, 23 Jun 2021 03:54:23 +0300 Subject: [PATCH 08/12] AND-1099 added an assetsRepository for jsonTests --- assets/json/junit_report_example.xml | 14 ++ assets/json/test_json_1.json | 188 ++++++++++++++++++ assets/json/test_json_2.json | 188 ++++++++++++++++++ .../domain/repository/repositories.dart | 48 ++++- .../ui/screen/json_test_launcher_screen.dart | 7 +- pubspec.yaml | 1 + 6 files changed, 442 insertions(+), 4 deletions(-) create mode 100644 assets/json/junit_report_example.xml create mode 100644 assets/json/test_json_1.json create mode 100644 assets/json/test_json_2.json diff --git a/assets/json/junit_report_example.xml b/assets/json/junit_report_example.xml new file mode 100644 index 0000000..c6415f7 --- /dev/null +++ b/assets/json/junit_report_example.xml @@ -0,0 +1,14 @@ + + + + + + WARNING: Use a program name that matches the source file name + Category: COBOL Code Review – Naming Conventions + File: /project/PROGRAM.cbl + Line: 2 + + + + \ No newline at end of file diff --git a/assets/json/test_json_1.json b/assets/json/test_json_1.json new file mode 100644 index 0000000..3843ab0 --- /dev/null +++ b/assets/json/test_json_1.json @@ -0,0 +1,188 @@ +{ + "setup": { + "name": "Twins", + "description": "Two test iterations with two steps iterations", + "personalizationConfig": { + "config": { + "issuerName": "TANGEM SDK", + "acquirerName": "Smart Cash", + "series": "BB", + "startNumber": 300000000000, + "count": 0, + "pin": "000000", + "pin2": "000", + "pin3": "", + "hexCrExKey": "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF", + "cvc": "000", + "pauseBeforePin2": 5000, + "smartSecurityDelay": true, + "curveID": "Secp256k1", + "signingMethods": [ + "SignHash" + ], + "maxSignatures": 999999, + "isReusable": true, + "allowSetPIN1": true, + "allowSetPIN2": true, + "useActivation": false, + "useCvc": false, + "useNDEF": true, + "useDynamicNDEF": true, + "useOneCommandAtTime": false, + "useBlock": false, + "allowSelectBlockchain": true, + "prohibitPurgeWallet": false, + "allowUnencrypted": true, + "allowFastEncryption": true, + "protectIssuerDataAgainstReplay": false, + "prohibitDefaultPIN1": false, + "disablePrecomputedNDEF": false, + "skipSecurityDelayIfValidatedByIssuer": true, + "skipCheckPIN2CVCIfValidatedByIssuer": true, + "skipSecurityDelayIfValidatedByLinkedTerminal": true, + "restrictOverwriteIssuerExtraData": false, + "requireTerminalTxSignature": false, + "requireTerminalCertSignature": false, + "checkPIN3OnCard": false, + "createWallet": true, + "walletsCount": 1, + "cardData": { + "batchId": "FFFF", + "blockchainName": "ETH", + "issuerName": "TANGEM SDK", + "manufacturerSignature": null, + "manufactureDateTime": "2021-06-22", + "productMask": [ + "Note" + ] + }, + "ndefRecords": [ + { + "type": "AAR", + "value": "com.tangem.wallet" + }, + { + "type": "URI", + "value": "https://tangem.com" + } + ] + }, + "issuer": { + "name": "TANGEM SDK", + "id": "TANGEM SDK\u0000", + "dataKeyPair": { + "publicKey": "045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26", + "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92" + }, + "transactionKeyPair": { + "publicKey": "0484c5192e9bfa6c528a344f442137a92b89ea835bfef1d04cb4362eb906b508c5889846cfea71ba6dc7b3120c2208df9c46127d3d85cb5cfbd1479e97133a39d8", + "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405081918171615141312" + } + }, + "acquirer": { + "name": "Smart Cash", + "id": "Smart Cash\u0000", + "keyPair": { + "publicKey": "0456ad1a82b22bcb40c38fd08939f87e6b80e40dec5b3bdb351c55fcd709e47f9fb2ed00c2304d3a986f79c5ae0ac3c84e88da46dc8f513b7542c716af8c9a2daf", + "privateKey": "21222324252627284771ED81F2BACF57479E4735EB1405083927372D40DA9E92" + } + }, + "manufacturer": { + "name": "Tangem", + "keyPair": { + "publicKey": "04bab86d56298c996f564a84fc88e28aed38184b12f07e519113bef48c76f3df3adc303599b08ac05b55ec3df98d9338573a6242f76f5d28f4f0f364e87e8fca2f", + "privateKey": "1b48cfd24bbb5b394771ed81f2bacf57479e4735eb1405083927372d40da9e92" + } + } + }, + "sdkConfig": {}, + "minimalFirmware": null, + "platform": null, + "iterations": 2, + "creationDateMs": 1624407062610 + }, + "steps": [ + { + "name": "Scan card", + "method": "SCAN_TASK", + "parameters": {}, + "expectedResult": { + "cardId": "BB03000000000004", + "manufacturerName": "TANGEM", + "status": "Loaded", + "firmwareVersion": { + "version": "3.37d SDK" + }, + "cardPublicKey": "0497C0424AF7BF8CE9920CB90EDAC2010FDED904937BC6D1D6E82E9254E0EFE4D4FDF355BB16BD0E9550D8BE5AB741B897FBC70360E99A86B97BA7DCC2FBF77D7E", + "defaultCurve": "Secp256k1", + "settingsMask": [ + "IsReusable", + "AllowSetPIN1", + "AllowSetPIN2", + "UseNDEF", + "UseDynamicNDEF", + "SmartSecurityDelay", + "AllowUnencrypted", + "AllowFastEncryption", + "AllowSelectBlockchain", + "SkipSecurityDelayIfValidatedByLinkedTerminal", + "SkipCheckPIN2CVCIfValidatedByIssuer", + "SkipSecurityDelayIfValidatedByIssuer" + ], + "issuerPublicKey": "045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26", + "signingMethods": [ + "SignHash" + ], + "pauseBeforePin2": 500, + "walletsCount": null, + "walletIndex": null, + "health": 0, + "isActivated": false, + "activationSeed": null, + "paymentFlowVersion": null, + "userCounter": null, + "userProtectedCounter": null, + "terminalIsLinked": false, + "cardData": { + "batchId": "FFFF", + "blockchainName": "ETH", + "issuerName": "TANGEM SDK", + "manufacturerSignature": "7CBEABA11F9D564A244260AC15CE0A26AB885811322C38B9FCA9E55EC7654C2027219ABB1BEAD190C2C370F48635129F76E8423FA8003DB6A08D2F64B1574004", + "manufactureDateTime": "2021-06-23", + "productMask": [ + "Note" + ] + }, + "isPin1Default": true, + "isPin2Default": true, + "wallets": [ + { + "index": 0, + "status": "Loaded", + "curve": "Secp256k1", + "settingsMask": [ + "IsReusable", + "AllowSetPIN1", + "AllowSetPIN2", + "UseNDEF", + "UseDynamicNDEF", + "SmartSecurityDelay", + "AllowUnencrypted", + "AllowFastEncryption", + "AllowSelectBlockchain", + "SkipSecurityDelayIfValidatedByLinkedTerminal", + "SkipCheckPIN2CVCIfValidatedByIssuer", + "SkipSecurityDelayIfValidatedByIssuer" + ], + "publicKey": "0408C8EF8DEC3B9C910B44D8A5C72138A333365ED2DC34E91FACA8B9A1B37EB5010CB0080E8144807998F50F6BE4B1CA4BF140ADFCEF34124D1417DD116535FB2D", + "signedHashes": 0, + "remainingSignatures": 999999 + } + ] + }, + "asserts": [], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 2 + } + ] +} \ No newline at end of file diff --git a/assets/json/test_json_2.json b/assets/json/test_json_2.json new file mode 100644 index 0000000..e5b44fd --- /dev/null +++ b/assets/json/test_json_2.json @@ -0,0 +1,188 @@ +{ + "setup": { + "name": "Twins 2", + "description": "Two test iterations with two steps iterations", + "personalizationConfig": { + "config": { + "issuerName": "TANGEM SDK", + "acquirerName": "Smart Cash", + "series": "BB", + "startNumber": 300000000000, + "count": 0, + "pin": "000000", + "pin2": "000", + "pin3": "", + "hexCrExKey": "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF", + "cvc": "000", + "pauseBeforePin2": 5000, + "smartSecurityDelay": true, + "curveID": "Secp256k1", + "signingMethods": [ + "SignHash" + ], + "maxSignatures": 999999, + "isReusable": true, + "allowSetPIN1": true, + "allowSetPIN2": true, + "useActivation": false, + "useCvc": false, + "useNDEF": true, + "useDynamicNDEF": true, + "useOneCommandAtTime": false, + "useBlock": false, + "allowSelectBlockchain": true, + "prohibitPurgeWallet": false, + "allowUnencrypted": true, + "allowFastEncryption": true, + "protectIssuerDataAgainstReplay": false, + "prohibitDefaultPIN1": false, + "disablePrecomputedNDEF": false, + "skipSecurityDelayIfValidatedByIssuer": true, + "skipCheckPIN2CVCIfValidatedByIssuer": true, + "skipSecurityDelayIfValidatedByLinkedTerminal": true, + "restrictOverwriteIssuerExtraData": false, + "requireTerminalTxSignature": false, + "requireTerminalCertSignature": false, + "checkPIN3OnCard": false, + "createWallet": true, + "walletsCount": 1, + "cardData": { + "batchId": "FFFF", + "blockchainName": "ETH", + "issuerName": "TANGEM SDK", + "manufacturerSignature": null, + "manufactureDateTime": "2021-06-22", + "productMask": [ + "Note" + ] + }, + "ndefRecords": [ + { + "type": "AAR", + "value": "com.tangem.wallet" + }, + { + "type": "URI", + "value": "https://tangem.com" + } + ] + }, + "issuer": { + "name": "TANGEM SDK", + "id": "TANGEM SDK\u0000", + "dataKeyPair": { + "publicKey": "045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26", + "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92" + }, + "transactionKeyPair": { + "publicKey": "0484c5192e9bfa6c528a344f442137a92b89ea835bfef1d04cb4362eb906b508c5889846cfea71ba6dc7b3120c2208df9c46127d3d85cb5cfbd1479e97133a39d8", + "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405081918171615141312" + } + }, + "acquirer": { + "name": "Smart Cash", + "id": "Smart Cash\u0000", + "keyPair": { + "publicKey": "0456ad1a82b22bcb40c38fd08939f87e6b80e40dec5b3bdb351c55fcd709e47f9fb2ed00c2304d3a986f79c5ae0ac3c84e88da46dc8f513b7542c716af8c9a2daf", + "privateKey": "21222324252627284771ED81F2BACF57479E4735EB1405083927372D40DA9E92" + } + }, + "manufacturer": { + "name": "Tangem", + "keyPair": { + "publicKey": "04bab86d56298c996f564a84fc88e28aed38184b12f07e519113bef48c76f3df3adc303599b08ac05b55ec3df98d9338573a6242f76f5d28f4f0f364e87e8fca2f", + "privateKey": "1b48cfd24bbb5b394771ed81f2bacf57479e4735eb1405083927372d40da9e92" + } + } + }, + "sdkConfig": {}, + "minimalFirmware": null, + "platform": null, + "iterations": 2, + "creationDateMs": 1624407062610 + }, + "steps": [ + { + "name": "Scan card", + "method": "SCAN_TASK", + "parameters": {}, + "expectedResult": { + "cardId": "BB03000000000004", + "manufacturerName": "TANGEM", + "status": "Loaded", + "firmwareVersion": { + "version": "3.37d SDK" + }, + "cardPublicKey": "0497C0424AF7BF8CE9920CB90EDAC2010FDED904937BC6D1D6E82E9254E0EFE4D4FDF355BB16BD0E9550D8BE5AB741B897FBC70360E99A86B97BA7DCC2FBF77D7E", + "defaultCurve": "Secp256k1", + "settingsMask": [ + "IsReusable", + "AllowSetPIN1", + "AllowSetPIN2", + "UseNDEF", + "UseDynamicNDEF", + "SmartSecurityDelay", + "AllowUnencrypted", + "AllowFastEncryption", + "AllowSelectBlockchain", + "SkipSecurityDelayIfValidatedByLinkedTerminal", + "SkipCheckPIN2CVCIfValidatedByIssuer", + "SkipSecurityDelayIfValidatedByIssuer" + ], + "issuerPublicKey": "045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26", + "signingMethods": [ + "SignHash" + ], + "pauseBeforePin2": 500, + "walletsCount": null, + "walletIndex": null, + "health": 0, + "isActivated": false, + "activationSeed": null, + "paymentFlowVersion": null, + "userCounter": null, + "userProtectedCounter": null, + "terminalIsLinked": false, + "cardData": { + "batchId": "FFFF", + "blockchainName": "ETH", + "issuerName": "TANGEM SDK", + "manufacturerSignature": "7CBEABA11F9D564A244260AC15CE0A26AB885811322C38B9FCA9E55EC7654C2027219ABB1BEAD190C2C370F48635129F76E8423FA8003DB6A08D2F64B1574004", + "manufactureDateTime": "2021-06-23", + "productMask": [ + "Note" + ] + }, + "isPin1Default": true, + "isPin2Default": true, + "wallets": [ + { + "index": 0, + "status": "Loaded", + "curve": "Secp256k1", + "settingsMask": [ + "IsReusable", + "AllowSetPIN1", + "AllowSetPIN2", + "UseNDEF", + "UseDynamicNDEF", + "SmartSecurityDelay", + "AllowUnencrypted", + "AllowFastEncryption", + "AllowSelectBlockchain", + "SkipSecurityDelayIfValidatedByLinkedTerminal", + "SkipCheckPIN2CVCIfValidatedByIssuer", + "SkipSecurityDelayIfValidatedByIssuer" + ], + "publicKey": "0408C8EF8DEC3B9C910B44D8A5C72138A333365ED2DC34E91FACA8B9A1B37EB5010CB0080E8144807998F50F6BE4B1CA4BF140ADFCEF34124D1417DD116535FB2D", + "signedHashes": 0, + "remainingSignatures": 999999 + } + ] + }, + "asserts": [], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 2 + } + ] +} \ No newline at end of file diff --git a/lib/app_test_launcher/domain/repository/repositories.dart b/lib/app_test_launcher/domain/repository/repositories.dart index edcf3f1..c5397a9 100644 --- a/lib/app_test_launcher/domain/repository/repositories.dart +++ b/lib/app_test_launcher/domain/repository/repositories.dart @@ -1,7 +1,9 @@ import 'dart:async'; +import 'dart:convert'; import 'package:devkit/app_test_assembler/domain/model/json_test_model.dart'; import 'package:devkit/app_test_assembler/domain/test_storages.dart'; +import 'package:flutter/services.dart' show rootBundle; import 'package:tangem_sdk/extensions/exp_extensions.dart'; abstract class JsonTestsRepository { @@ -12,7 +14,7 @@ abstract class JsonTestsRepository { JsonTest? get(String name); } -class JsonTestRepository implements JsonTestsRepository { +class JsonTestStorageRepository implements JsonTestsRepository { final JsonTestsStorage storage = JsonTestsStorage(); @override @@ -34,3 +36,47 @@ class JsonTestRepository implements JsonTestsRepository { return storage.names().map((e) => storage.get(e)).toList().toNullSafe(); } } + +class JsonTestAssetsRepository implements JsonTestsRepository { + List _jsonTests = []; + + @override + Future init() async { + final completer = Completer(); + + final manifestContent = await rootBundle.loadString('AssetManifest.json'); + + final Map manifestMap = json.decode(manifestContent); + // >> To get paths you need these 2 lines + + final testPaths = manifestMap.keys + .where((String key) => key.contains('json/')) + .where((String key) => key.contains('.json')) + .toList(); + + int inconvertibleCount = 0; + testPaths.forEach((element) { + rootBundle.loadString(element).then((value) { + try { + final map = jsonDecode(value); + _jsonTests.add(JsonTest.fromJson(map)); + } catch (ex) { + inconvertibleCount++; + } + if (testPaths.length - inconvertibleCount == _jsonTests.length) completer.complete(); + }); + }); + + return completer.future; + } + + @override + JsonTest? get(String name) { + return _jsonTests.firstWhereOrNull((e) => e.setup.name == name); + } + + @override + List getList() { + return _jsonTests; + } +} diff --git a/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart b/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart index df299be..d8a3ae6 100644 --- a/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart +++ b/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart @@ -25,7 +25,8 @@ class _JsonTestLauncherScreenState extends State { Widget build(BuildContext context) { return MultiRepositoryProvider( providers: [ - RepositoryProvider(create: (context) => JsonTestLauncherBloc().apply((it) => _bloc = it)), + RepositoryProvider( + create: (context) => JsonTestLauncherBloc(JsonTestAssetsRepository()).apply((it) => _bloc = it)), ], child: JsonTestLauncherFrame(), ); @@ -99,10 +100,10 @@ class _JsonTestLauncherBodyState extends State { } class JsonTestLauncherBloc extends BaseBloc { - final JsonTestRepository repo = JsonTestRepository(); + final JsonTestsRepository repo; final bsJsonTests = BehaviorSubject>(); - JsonTestLauncherBloc() { + JsonTestLauncherBloc(this.repo) { addSubject(bsJsonTests); initRepository(); } diff --git a/pubspec.yaml b/pubspec.yaml index 01faf1e..8b984ea 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -94,6 +94,7 @@ flutter: assets: - assets/ - assets/icons/ + - assets/json/ # To add custom fonts to your application, add a fonts section here, From aee5ef0857b2c1b0983f8f7c10e2063d3d290f6b Mon Sep 17 00:00:00 2001 From: Anton Zhilenkov Date: Wed, 23 Jun 2021 03:56:49 +0300 Subject: [PATCH 09/12] AND-1099 launches de\personalize commands before running a test --- lib/app/domain/model/command_data_models.dart | 11 +- .../domain/model/json_test_model.dart | 20 ++ .../domain/common/test_result.dart | 26 ++- lib/app_test_launcher/domain/error/error.dart | 25 ++- .../domain/error/test_assert_error.dart | 2 +- .../domain/error/test_error.dart | 21 -- .../domain/error/test_executable_error.dart | 33 --- .../domain/error/test_step_error.dart | 12 + .../domain/executable/assert/assert.dart | 4 +- .../executable/assert/assert_launcher.dart | 2 +- .../domain/executable/step/step_launcher.dart | 53 +++-- .../domain/test_launcher.dart | 210 ++++++++++------- .../domain/variable_service.dart | 16 +- .../tangem-sdk-flutter/android/build.gradle | 8 +- .../plugin/tangem_sdk/TangemSdkPlugin.kt | 212 +++++++++--------- .../lib/card_responses/card_response.g.dart | 96 +++++--- .../lib/model/json_rpc.dart | 103 ++------- .../lib/model/json_rpc.g.dart | 18 +- .../tangem-sdk-flutter/lib/plugin_error.dart | 8 +- plugin/tangem-sdk-flutter/lib/sdk_plugin.dart | 48 +++- 20 files changed, 522 insertions(+), 406 deletions(-) delete mode 100644 lib/app_test_launcher/domain/error/test_error.dart delete mode 100644 lib/app_test_launcher/domain/error/test_executable_error.dart create mode 100644 lib/app_test_launcher/domain/error/test_step_error.dart diff --git a/lib/app/domain/model/command_data_models.dart b/lib/app/domain/model/command_data_models.dart index 3385185..4323fcb 100644 --- a/lib/app/domain/model/command_data_models.dart +++ b/lib/app/domain/model/command_data_models.dart @@ -36,13 +36,22 @@ class SignModel extends CommandDataModel { class PersonalizationModel extends CommandDataModel { final PersonalizationConfig config; final Issuer issuer; + final Manufacturer manufacturer; + final Acquirer? acquirer; - PersonalizationModel(this.config, this.issuer) : super(TangemSdk.cPersonalize); + PersonalizationModel( + this.config, + this.issuer, + this.manufacturer, [ + this.acquirer, + ]) : super(TangemSdk.cPersonalize); factory PersonalizationModel.fromJson(Map json) { final model = PersonalizationModel( PersonalizationConfig.fromJson(json["config"]), Issuer.fromJson(json["issuer"]), + Manufacturer.fromJson(json["manufacturer"]), + Acquirer.fromJson(json["acquirer"]), ); return CommandDataModel.attachBaseData(model, json); } diff --git a/lib/app_test_assembler/domain/model/json_test_model.dart b/lib/app_test_assembler/domain/model/json_test_model.dart index cdfa78f..c6eec32 100644 --- a/lib/app_test_assembler/domain/model/json_test_model.dart +++ b/lib/app_test_assembler/domain/model/json_test_model.dart @@ -119,6 +119,26 @@ class StepModel { factory StepModel.fromJson(Map json) => _$TestStepFromJson(json); Map toJson() => _$TestStepToJson(this); + + StepModel copyWith({ + String? name, + String? method, + Map? params, + Map? expectedResult, + List? asserts, + String? actionType, + int? iterations, + }) { + return StepModel( + name ?? this.name, + method ?? this.method, + params ?? this.params, + expectedResult ?? this.expectedResult, + asserts ?? this.asserts, + actionType ?? this.actionType, + iterations ?? this.iterations, + ); + } } @JsonSerializable() diff --git a/lib/app_test_launcher/domain/common/test_result.dart b/lib/app_test_launcher/domain/common/test_result.dart index e93caf0..0f39090 100644 --- a/lib/app_test_launcher/domain/common/test_result.dart +++ b/lib/app_test_launcher/domain/common/test_result.dart @@ -1,4 +1,3 @@ - import 'package:devkit/app_test_launcher/domain/error/error.dart'; class TestResult {} @@ -9,8 +8,33 @@ class Success implements TestResult { Success([this.name]); } +class StepSuccess extends Success { + StepSuccess(String name) : super(name); +} + +class AssertSuccess extends Success { + AssertSuccess(String name) : super(name); +} + class Failure implements TestResult { final TestFrameworkError error; Failure(this.error); } + +class StepFailure extends Failure { + final String stepName; + final String? assertName; + + StepFailure(this.stepName, TestFrameworkError error, [this.assertName]) : super(error); + + factory StepFailure.fromAssert(String stepName, AssertFailure failure) { + return StepFailure(stepName, failure.error, failure.assertName); + } +} + +class AssertFailure extends Failure { + final String assertName; + + AssertFailure(this.assertName, TestFrameworkError error) : super(error); +} diff --git a/lib/app_test_launcher/domain/error/error.dart b/lib/app_test_launcher/domain/error/error.dart index ca932e9..3f4b123 100644 --- a/lib/app_test_launcher/domain/error/error.dart +++ b/lib/app_test_launcher/domain/error/error.dart @@ -1,12 +1,29 @@ +import 'package:tangem_sdk/model/json_rpc.dart'; +import 'package:tangem_sdk/plugin_error.dart'; + abstract class TestFrameworkError { String get errorMessage; } -class CustomError implements TestFrameworkError { - final String _customError; +class TangemSdkPluginWrappedError extends TestFrameworkError { + final TangemSdkPluginError error; - CustomError(this._customError); + TangemSdkPluginWrappedError(this.error); @override - String get errorMessage => _customError; + String get errorMessage => "TangemSdkPluginError: ${error.toString()}"; +} + +extension OnJSONRPCError on JSONRPCError { + bool isInterruptTest() { + switch (code) { + case 1000: + return true; + case 50002: + return true; + case 50003: + return true; + } + return false; + } } diff --git a/lib/app_test_launcher/domain/error/test_assert_error.dart b/lib/app_test_launcher/domain/error/test_assert_error.dart index 5250e1a..05bdc6e 100644 --- a/lib/app_test_launcher/domain/error/test_assert_error.dart +++ b/lib/app_test_launcher/domain/error/test_assert_error.dart @@ -1,6 +1,6 @@ import 'error.dart'; -abstract class TestAssertError implements TestFrameworkError {} +abstract class TestAssertError extends TestFrameworkError {} class ExpectedAndActualResultError extends TestAssertError { final String? message; diff --git a/lib/app_test_launcher/domain/error/test_error.dart b/lib/app_test_launcher/domain/error/test_error.dart deleted file mode 100644 index cf2f811..0000000 --- a/lib/app_test_launcher/domain/error/test_error.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'dart:core'; - -import 'error.dart'; - - -abstract class TestError extends TestFrameworkError {} - -class TestIsEmptyError implements TestError { - @override - String get errorMessage => "Test doesn't contains any data to proceed"; -} - -class SessionSdkInitError implements TestError { - final dynamic error; - - SessionSdkInitError(this.error); - - @override - String get errorMessage => "Session initialization failed. Code: ${error.code}, message: ${error.customMessage}"; -} - diff --git a/lib/app_test_launcher/domain/error/test_executable_error.dart b/lib/app_test_launcher/domain/error/test_executable_error.dart deleted file mode 100644 index c1706c2..0000000 --- a/lib/app_test_launcher/domain/error/test_executable_error.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'error.dart'; - -abstract class ExecutableError implements TestFrameworkError {} - -class ExecutableNotFoundError implements ExecutableError { - final String name; - - ExecutableNotFoundError(this.name); - - @override - String get errorMessage => "Executable is not found for the name: $name"; -} - -class FetchVariableError implements ExecutableError { - final dynamic paramName; - final String path; - final AssertionError exception; - - FetchVariableError(this.paramName, this.path, this.exception); - - @override - String get errorMessage => - "Fetching variable failed. Name: $paramName, path: $path, ex: ${exception.message?.toString()}"; -} - -class ExpectedResultError implements ExecutableError { - final List errorMessages; - - ExpectedResultError(this.errorMessages); - - @override - String get errorMessage => errorMessages.join("\n"); -} diff --git a/lib/app_test_launcher/domain/error/test_step_error.dart b/lib/app_test_launcher/domain/error/test_step_error.dart new file mode 100644 index 0000000..6b0568f --- /dev/null +++ b/lib/app_test_launcher/domain/error/test_step_error.dart @@ -0,0 +1,12 @@ +import 'error.dart'; + +abstract class TestStepError extends TestFrameworkError {} + +class AssertNotRegisteredError extends TestStepError { + final String name; + + AssertNotRegisteredError(this.name); + + @override + String get errorMessage => "Executable is not found for the name: $name"; +} diff --git a/lib/app_test_launcher/domain/executable/assert/assert.dart b/lib/app_test_launcher/domain/executable/assert/assert.dart index 1ef160f..935523e 100644 --- a/lib/app_test_launcher/domain/executable/assert/assert.dart +++ b/lib/app_test_launcher/domain/executable/assert/assert.dart @@ -41,9 +41,9 @@ class EqualsAssert extends TestAssert { final firstValue = getFieldValue(fields[0]); final secondValue = getFieldValue(fields[1]); if (firstValue == secondValue) { - callback(Success(_type)); + callback(AssertSuccess(_type)); } else { - callback(Failure(EqualsError(firstValue, secondValue))); + callback(AssertFailure(_type, EqualsError(firstValue, secondValue))); } } } diff --git a/lib/app_test_launcher/domain/executable/assert/assert_launcher.dart b/lib/app_test_launcher/domain/executable/assert/assert_launcher.dart index 9ba25c5..af077af 100644 --- a/lib/app_test_launcher/domain/executable/assert/assert_launcher.dart +++ b/lib/app_test_launcher/domain/executable/assert/assert_launcher.dart @@ -22,7 +22,7 @@ class AssertsLauncher implements Executable { callback(Success()); } else { testAssert.run((result) { - if (result is Success) { + if (result is AssertSuccess) { _executeAssert(_assertsQueue.poll(), callback); } else { callback(result); diff --git a/lib/app_test_launcher/domain/executable/step/step_launcher.dart b/lib/app_test_launcher/domain/executable/step/step_launcher.dart index 91ee777..38c5608 100644 --- a/lib/app_test_launcher/domain/executable/step/step_launcher.dart +++ b/lib/app_test_launcher/domain/executable/step/step_launcher.dart @@ -1,12 +1,11 @@ import 'dart:collection'; -import 'dart:convert'; import 'package:devkit/app_test_assembler/domain/model/json_test_model.dart'; import 'package:devkit/app_test_launcher/domain/common/test_result.dart'; import 'package:devkit/app_test_launcher/domain/common/typedefs.dart'; import 'package:devkit/app_test_launcher/domain/error/error.dart'; import 'package:devkit/app_test_launcher/domain/error/test_assert_error.dart'; -import 'package:devkit/app_test_launcher/domain/error/test_executable_error.dart'; +import 'package:devkit/app_test_launcher/domain/error/test_step_error.dart'; import 'package:devkit/app_test_launcher/domain/executable/assert/assert.dart'; import 'package:devkit/app_test_launcher/domain/executable/assert/assert_launcher.dart'; import 'package:devkit/app_test_launcher/domain/executable/executable.dart'; @@ -26,15 +25,26 @@ class StepLauncher implements Executable { _fetchVariables(); final jsonRpcRequestCallback = Callback((result) { - final error = _checkExpectedWithActualResult(_model.expectedResult, result); - if (error != null) { - callback(Failure(error)); - return; + final jsonRpcResponse = result as JSONRPCResponse; + if (jsonRpcResponse.result == null && jsonRpcResponse.error != null) { + if (jsonRpcResponse.error?.isInterruptTest() == true) { + // TODO: приудмать что отправить + // callback(Failure()); + return; + } else { + VariableService.registerError(_model.name, jsonRpcResponse.error); + } + } else { + final error = _checkExpectedWithActualResult(_model.expectedResult, jsonRpcResponse); + if (error != null) { + callback(StepFailure(_model.name, error)); + return; + } + VariableService.registerActualResult(_model.name, jsonRpcResponse.result); } - VariableService.registerResult(_model.name, result); _executeAsserts(callback); }, (error) { - callback(Failure(CustomError(jsonEncode(error)))); + callback(StepFailure(_model.name, TangemSdkPluginWrappedError(error))); }); TangemSdk.runJSONRPCRequest(jsonRpcRequestCallback, JSONRPCRequest(_model.method, _model.params)); @@ -52,7 +62,7 @@ class StepLauncher implements Executable { void _executeAsserts(OnComplete callback) { if (_model.asserts.isEmpty) { - callback(Success()); + callback(StepSuccess(_model.name)); return; } @@ -60,7 +70,7 @@ class StepLauncher implements Executable { _model.asserts.forEach((element) { final testAssert = _assertsFactory.getAssert(element.type); if (testAssert == null) { - callback(Failure(ExecutableNotFoundError(element.type))); + callback(StepFailure(_model.name, AssertNotRegisteredError(element.type))); return; } @@ -68,23 +78,26 @@ class StepLauncher implements Executable { assertsQueue.add(testAssert); }); - AssertsLauncher(assertsQueue).run(callback); + AssertsLauncher(assertsQueue).run((result) { + if (result is Success) { + callback(StepSuccess(_model.name)); + } else { + callback(StepFailure.fromAssert(_model.name, result as AssertFailure)); + } + }); } - TestAssertError? _checkExpectedWithActualResult(Map expResult, dynamic actualResult) { - if (actualResult is! Map) return ExpectedAndActualResultError("Actual result is not a map"); - - final jsonRpcResult = actualResult as Map; - final jsonRpcResponse = JSONRPCResponse.fromJson(jsonRpcResult); + TestAssertError? _checkExpectedWithActualResult(Map expResult, JSONRPCResponse jsonRpcResponse) { if (jsonRpcResponse.result is! Map) return ExpectedAndActualResultError("JsonRpc result is not a map"); - final result = jsonRpcResponse.result as Map; + return null; - if (expResult.length != result.length) return ExpectedAndActualResultError("Results length doesn't match"); + final actualResult = jsonRpcResponse.result as Map; + if (expResult.length != actualResult.length) return ExpectedAndActualResultError("Results length doesn't match"); - final missedKeys = expResult.keys.toList()..removeWhere((element) => result.containsKey(element)); - final unexpectedKeys = result.keys.toList()..removeWhere((element) => expResult.containsKey(element)); + final missedKeys = expResult.keys.toList()..removeWhere((element) => actualResult.containsKey(element)); + final unexpectedKeys = actualResult.keys.toList()..removeWhere((element) => expResult.containsKey(element)); if (missedKeys.isNotEmpty || unexpectedKeys.isNotEmpty) { final missed = "Missed keys in the actual result: [${missedKeys.join(", ")}]"; diff --git a/lib/app_test_launcher/domain/test_launcher.dart b/lib/app_test_launcher/domain/test_launcher.dart index e547edb..3f55fb1 100644 --- a/lib/app_test_launcher/domain/test_launcher.dart +++ b/lib/app_test_launcher/domain/test_launcher.dart @@ -1,133 +1,187 @@ -import 'dart:async'; import 'dart:collection'; import 'dart:convert'; import 'package:devkit/app_test_assembler/domain/model/json_test_model.dart'; +import 'package:devkit/app_test_launcher/domain/error/error.dart'; +import 'package:devkit/app_test_launcher/domain/error/test_step_error.dart'; import 'package:tangem_sdk/extensions/exp_extensions.dart'; import 'package:tangem_sdk/tangem_sdk.dart'; import 'common/test_result.dart'; import 'common/typedefs.dart'; -import 'error/error.dart'; -import 'error/test_error.dart'; +import 'error/test_assert_error.dart'; import 'executable/assert/assert.dart'; import 'executable/step/step_launcher.dart'; import 'variable_service.dart'; class TestLauncher { + static final int defaultIterationsCount = 1; + final JsonTest _jsonTest; final AssertsFactory _assertsFactory; - OnComplete? onTestComplete; - Queue _testQueue = Queue(); - Queue _stepQueue = Queue(); + Queue _stepQueue = Queue(); + + late JsonTest _testTemplate; + late StepModel _stepTemplate; + + int _testIterationsLeft = defaultIterationsCount; + int _stepIterationsLeft = defaultIterationsCount; + + List _successSteps = []; + StepFailure? _stepFailure; TestLauncher(this._jsonTest, this._assertsFactory); - void launch() { - _startSession(() { - _testQueue = _generateTestQueue(); - final nextTest = _testQueue.poll(); - if (nextTest == null) { - onTestComplete?.call(Failure(TestIsEmptyError())); - } else { - _runTest(nextTest); - } - }, (startingSessionError) { - onTestComplete?.call(Failure(startingSessionError)); - }); + void launch() async { + if (_jsonTest.steps.isEmpty) { + onTestComplete?.call(Success(_jsonTest.setup.name)); + return; + } + + _testTemplate = _jsonTest.copyWith(); + _testIterationsLeft = _jsonTest.setup.iterations ?? defaultIterationsCount; + _startNewTest(_jsonTest); } - void _startSession(Function onSuccess, Function(dynamic error) onError) { - TangemSdk.startSession(Callback((success) => onSuccess(), onError), { - TangemSdk.initialMessage: _jsonTest.setup.sdkConfig[TangemSdk.initialMessage], - TangemSdk.cardId: _jsonTest.setup.sdkConfig[TangemSdk.cardId], + void _startNewTest(JsonTest test) async { + await Future.delayed(Duration(milliseconds: 1000)); + if (_testIterationsLeft == 0) { + onTestComplete?.call(Success(_jsonTest.setup.name)); + return; + } + + print(""); + print("Test: ${test.setup.name}: Start"); + _prepare(() async { + print("Test: Prepare: Complete"); + await Future.delayed(Duration(milliseconds: 1000)); + _startSession(() { + _runTest(test); + }); }); } - void _stopSession(Function onSuccess, Function(dynamic error) onError) { - TangemSdk.stopSession(Callback((success) => onSuccess(), (error) => onError(error))); + void _runTest(JsonTest test) { + print("Test: ${test.setup.name}: Run: iterations left: $_testIterationsLeft"); + _testIterationsLeft--; + _stepQueue = Queue.from(test.steps); + final nextStep = _stepQueue.poll(); + if (nextStep == null) { + _onStepSequenceComplete(Success(test.setup.name)); + } else { + _runStep(nextStep); + } } - void _runTest(JsonTest test) { - VariableService.reset(); - _prepare().then((value) { - _stepQueue = Queue.from(test.steps); - final nextStep = _stepQueue.poll(); - if (nextStep == null) { - _onStepSequenceComplete(Success(test.setup.name)); - } else { - _runStep(nextStep); - } - }).onError((error, stackTrace) { - _handleError(error); - }); + void _runStep(StepModel step, [bool isNewIteration = true]) async{ + await Future.delayed(Duration(milliseconds: 300)); + if (isNewIteration) { + _stepTemplate = step.copyWith(); + _stepIterationsLeft = step.iterations ?? defaultIterationsCount; + } + print("Test: ${_testTemplate.setup.name}: Step: ${step.name}: Run: iterations left: $_stepIterationsLeft"); + _stepIterationsLeft--; + StepLauncher(step, _assertsFactory).run(_onStepComplete); } void _onStepComplete(TestResult result) { if (result is Success) { - final nextStep = _stepQueue.poll(); - if (nextStep == null) { - _onStepSequenceComplete(result); + final stepName = result.name ?? "Undefined"; + _successSteps.add(stepName); + print("Test: ${_testTemplate.setup.name}: Step: $stepName: Complete"); + if (_stepIterationsLeft == 0) { + final nextStep = _stepQueue.poll(); + if (nextStep == null) { + _onStepSequenceComplete(result); + } else { + _runStep(nextStep); + } } else { - _runStep(nextStep); + _runStep(_stepTemplate, false); } } else { _onStepSequenceComplete(result); } } - void _runStep(StepModel step) { - StepLauncher(step, _assertsFactory).run(_onStepComplete); - } - void _onStepSequenceComplete(TestResult result) { + print("Test: ${_testTemplate.setup.name}: Complete"); if (result is Success) { - final nextTest = _testQueue.poll(); - if (nextTest == null) { - _stopSession((){ - onTestComplete?.call(Success(_jsonTest.setup.name)); - }, (error) { - _handleError(error); - }); - } else { - _runTest(nextTest); - } + _stopSession(() { + _startNewTest(_testTemplate); + }); } else { - _handleError(result); + _stopSession(() { + _onStepError(result as StepFailure); + }); } } - void _handleError(dynamic error) { - onTestComplete?.call(Failure(CustomError(jsonEncode(error)))); + void _prepare(Function onSuccess) { + print("Test: Prepare"); + VariableService.reset(); + _stepFailure = null; + _rePersonalize(onSuccess); } - Queue _generateTestQueue() { - final tests = []; - if (_jsonTest.setup.iterations == null) { - tests.add(_jsonTest); - } else { - _jsonTest.setup.iterations?.foreach((e) => tests.add(_jsonTest)); - } - return Queue.from(tests); + void _rePersonalize(Function onSuccess) { + final depersonalize = JSONRPCRequest(TangemSdk.getJsonRpcMethod(TangemSdk.cDepersonalize)!, {}); + final personalize = JSONRPCRequest( + TangemSdk.getJsonRpcMethod(TangemSdk.cPersonalize)!, + _jsonTest.setup.personalizationConfig, + ); + + _startSession(() { + print("Test: Prepare: Run: DE/PERSONALIZE"); + TangemSdk.runJSONRPCRequest( + Callback((_) { + TangemSdk.runJSONRPCRequest(Callback((_) => _stopSession(onSuccess), _onSdkError), personalize); + }, _onSdkError), + depersonalize); + }); } - Future _prepare() { - final completer = Completer(); - _configureSdk(() { - _rePersonalize(() { - completer.complete(); - }, _handleError); - }, _handleError); - return completer.future; + // все ошибки TangemSdkPluginError должны прерывать выполнение теста, т.к. они формируется + // не в результате работы jsonRPC + + _onSdkError(dynamic error) { + print("Error type: ${error.runtimeType.toString()}"); + print(jsonEncode(error)); } - void _configureSdk(Function onSuccess, Function(dynamic error) onError) { - onSuccess(); + _onStepError(StepFailure failure) { + _stepFailure = failure; + if (failure.error is TangemSdkPluginWrappedError) { + } else if (failure.error is TestAssertError) { + print("Assert failure: ${failure.error.runtimeType.toString()}"); + print(jsonEncode(failure.error)); + } else if (failure.error is TestStepError) { + print("Step failure: ${failure.error.runtimeType.toString()}"); + print(jsonEncode(failure.error)); + } else { + print("Some undefined failure: ${failure.error.runtimeType.toString()}"); + print(jsonEncode(failure.error)); + } + } + + void _startSession(Function onSuccess) { + // print("Starting session..."); + TangemSdk.startSession(Callback((success) { + print("Session started +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); + onSuccess(); + }, _onSdkError), { + TangemSdk.initialMessage: _jsonTest.setup.sdkConfig[TangemSdk.initialMessage], + TangemSdk.cardId: _jsonTest.setup.sdkConfig[TangemSdk.cardId], + }); } - void _rePersonalize(Function onSuccess, Function(dynamic error) onError) { - onSuccess(); + void _stopSession([Function? onSuccess]) { + // print("Stopping session..."); + TangemSdk.stopSession(Callback((success) { + print("Session stopped ---------------------------------------------------------------------"); + onSuccess?.call(); + }, _onSdkError)); } } diff --git a/lib/app_test_launcher/domain/variable_service.dart b/lib/app_test_launcher/domain/variable_service.dart index 06bffb5..1b33ac6 100644 --- a/lib/app_test_launcher/domain/variable_service.dart +++ b/lib/app_test_launcher/domain/variable_service.dart @@ -1,6 +1,5 @@ import 'package:devkit/app_test_launcher/domain/common/typedefs.dart'; import 'package:tangem_sdk/extensions/exp_extensions.dart'; -import 'package:tangem_sdk/model/json_rpc.dart'; class VariableService { static final _variablePattern = RegExp("\\{[^\\{\\}]*\\}"); @@ -10,6 +9,7 @@ class VariableService { static final _stepPointer = "#"; static final _parent = "#parent"; static final _actualResult = "actualResult"; + static final _error = "error"; static final _stepValues = {}; @@ -17,14 +17,24 @@ class VariableService { _stepValues.clear(); } - static void registerResult(String name, JSONRPCResponse response) { + static void registerActualResult(String name, dynamic result) { final stepMap = _stepValues[name]; if (stepMap == null) { // Step is not registered return; } - stepMap[_actualResult] = response.result; + stepMap[_actualResult] = result; + } + + static void registerError(String name, dynamic result) { + final stepMap = _stepValues[name]; + if (stepMap == null) { + // Step is not registered + return; + } + + stepMap[_error] = result; } static void registerStep(String name, SourceMap source) { diff --git a/plugin/tangem-sdk-flutter/android/build.gradle b/plugin/tangem-sdk-flutter/android/build.gradle index 4934c79..2c003c7 100644 --- a/plugin/tangem-sdk-flutter/android/build.gradle +++ b/plugin/tangem-sdk-flutter/android/build.gradle @@ -53,12 +53,12 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" -// implementation 'com.tangem.tangem-sdk-kotlin:core:develop-43' -// implementation 'com.tangem.tangem-sdk-kotlin:android:develop-43' + implementation 'com.tangem.tangem-sdk-kotlin:core:develop-46' + implementation 'com.tangem.tangem-sdk-kotlin:android:develop-46' // local dependencies - implementation 'com.tangem.tangem-sdk-kotlin:core:0.0.1' - implementation 'com.tangem.tangem-sdk-kotlin:android:0.0.1' +// implementation 'com.tangem.tangem-sdk-kotlin:core:0.0.1' +// implementation 'com.tangem.tangem-sdk-kotlin:android:0.0.1' implementation "com.squareup.sqldelight:android-driver:1.4.0" diff --git a/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt b/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt index 4143c9d..fef8d5f 100644 --- a/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt +++ b/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt @@ -10,7 +10,6 @@ import com.squareup.sqldelight.android.AndroidSqliteDriver import com.tangem.* import com.tangem.commands.common.card.FirmwareType import com.tangem.commands.common.jsonConverter.MoshiJsonConverter -import com.tangem.commands.common.jsonRpc.JSONRPCResponse import com.tangem.commands.file.DataToWrite import com.tangem.commands.file.FileSettingsChange import com.tangem.common.CardValuesDbStorage @@ -96,6 +95,9 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { replyAlreadySubmit = false when (call.method) { + "startSession" -> startSession(call, result) + "stopSession" -> stopSession(call, result) + "runJSONRPCRequest" -> runJSONRPCRequest(call, result) "allowsOnlyDebugCards" -> allowsOnlyDebugCards(call, result) "scanCard" -> scanCard(call, result) "sign" -> sign(call, result) @@ -117,14 +119,108 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { "deleteFiles" -> deleteFiles(call, result) "changeFilesSettings" -> changeFilesSettings(call, result) "prepareHashes" -> prepareHashes(call, result) - "startSession" -> startSession(call, result) - "stopSession" -> stopSession(call, result) - "JSONRPCRequest" -> runJSONRPCRequest(call, result) "getPlatformVersion" -> result.success("Android ${android.os.Build.VERSION.RELEASE}") else -> result.notImplemented() } } + private fun startSession(call: MethodCall, result: Result) { + try { + if (cardSession != null && cardSession !!.state == CardSessionState.Active) + throw PluginException("The CardSession has already started") + + sdk.startSession( + call.extractOptional("cardId"), + call.extractOptional("initialMessage"), + ) { session, error -> + if (error == null) { + cardSession = session + handleResult(result, CompletionResult.Success(true)) + } else { + cardSession = null + handleResult(result, CompletionResult.Failure(error)) + } + } + } catch (ex: Exception) { + handleException(result, ex) + } + } + + private fun stopSession(call: MethodCall, result: Result) { + try { + val session = cardSession ?: throw PluginException("Session not started") + session.stop() + cardSession = null + handleResult(result, CompletionResult.Success(true)) + } catch (ex: Exception) { + handleException(result, ex) + } + } + + private fun runJSONRPCRequest(call: MethodCall, result: Result) { + try { + replyAlreadySubmit = false + val stringOfJSONRPCRequest = call.extract("JSONRPCRequest") + + val callback = callbackWithResult@{ response: String -> + if (! replyAlreadySubmit) { + replyAlreadySubmit = true + handler.post { result.success(response) } + } + } + + if (cardSession == null) { + sdk.startSessionWithJsonRequest( + stringOfJSONRPCRequest, + call.extractOptional("cardId"), + call.extractOptional("initialMessage"), + callback + ) + } else { + cardSession !!.run(stringOfJSONRPCRequest, callback) + } + } catch (ex: Exception) { + handleException(result, ex) + } + } + + private fun handleResult(result: Result, completionResult: CompletionResult<*>) { + if (replyAlreadySubmit) return + replyAlreadySubmit = true + + when (completionResult) { + is CompletionResult.Success -> { + handler.post { result.success(converter.toJson(completionResult.data)) } + } + is CompletionResult.Failure -> { + val error = completionResult.error + val errorMessage = if (error is TangemSdkError) { + val activity = wActivity.get() + if (activity == null) error.customMessage else error.localizedDescription(activity) + } else { + error.customMessage + } + val pluginError = PluginError(error.code, errorMessage) + handler.post { + result.error("${error.code}", errorMessage, converter.toJson(pluginError)) + } + } + } + } + + private fun handleException(result: Result, ex: Exception) { + if (replyAlreadySubmit) return + replyAlreadySubmit = true + + val exception = ex as? PluginException ?: TangemSdkException(ex) + handler.post { + val code = 1000 + val localizedDescription: String = exception.toString() + result.error("$code", localizedDescription, + converter.toJson(PluginError(code, localizedDescription))) + } + } + private fun scanCard(call: MethodCall, result: Result) { try { sdk.scanCard( @@ -151,10 +247,10 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { private fun personalize(call: MethodCall, result: Result) { try { sdk.personalize( - call.extract("cardConfig"), + call.extract("config"), call.extract("issuer"), call.extract("manufacturer"), - call.extract("acquirer"), + call.extractOptional("acquirer"), call.extractOptional("initialMessage") ) { handleResult(result, it) } } catch (ex: Exception) { @@ -391,43 +487,6 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { } } - private fun handleResult(result: Result, completionResult: CompletionResult<*>) { - if (replyAlreadySubmit) return - replyAlreadySubmit = true - - when (completionResult) { - is CompletionResult.Success -> { - handler.post { result.success(converter.toJson(completionResult.data)) } - } - is CompletionResult.Failure -> { - val error = completionResult.error - val errorMessage = if (error is TangemSdkError) { - val activity = wActivity.get() - if (activity == null) error.customMessage else error.localizedDescription(activity) - } else { - error.customMessage - } - val pluginError = PluginError(error.code, errorMessage) - handler.post { - result.error("${error.code}", errorMessage, converter.toJson(pluginError)) - } - } - } - } - - private fun handleException(result: Result, ex: Exception) { - if (replyAlreadySubmit) return - replyAlreadySubmit = true - - val exception = ex as? PluginException ?: TangemSdkException(ex) - handler.post { - val code = 1000 - val localizedDescription: String = exception.toString() - result.error("$code", localizedDescription, - converter.toJson(PluginError(code, localizedDescription))) - } - } - @Throws(PluginException::class) inline fun MethodCall.extract(name: String): T { return try { @@ -473,73 +532,6 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { data class Delete(val indices: List?) data class ChangeSettings(val changes: List) } - - private fun startSession(call: MethodCall, result: Result) { - try { - if (cardSession != null && cardSession !!.state == CardSessionState.Active) - throw PluginException("The CardSession has already started") - - sdk.startSession( - call.extractOptional("cardId"), - call.extractOptional("initialMessage"), - ) { session, error -> - if (error == null) { - cardSession = session - handleResult(result, CompletionResult.Success(true)) - } else { - cardSession = null - handleResult(result, CompletionResult.Failure(error)) - } - } - } catch (ex: Exception) { - handleException(result, ex) - } - } - - private fun stopSession(call: MethodCall, result: Result) { - try { - val session = cardSession ?: throw PluginException("Session not started") - session.stop() - cardSession = null - handleResult(result, CompletionResult.Success(true)) - } catch (ex: Exception) { - handleException(result, ex) - } - } - - private fun runJSONRPCRequest(call: MethodCall, result: Result) { - try { - val stringOfJSONRPCRequest = call.extract("JSONRPCRequest") - - val callback = callbackWithResult@{ response: String -> - if (replyAlreadySubmit) return@callbackWithResult - - replyAlreadySubmit = true - val jsonRpcResponse = converter.fromJson(response) - ?: throw PluginException("Can't convert the string response to JSONRPCResponse") - - if (jsonRpcResponse.error == null) { - handler.post { result.success(response) } - } else { - val error = jsonRpcResponse.error !! - handler.post { result.error("${error.code}", error.message, response) } - } - } - - if (cardSession == null) { - sdk.startSessionWithJsonRequest( - stringOfJSONRPCRequest, - call.extractOptional("cardId"), - call.extractOptional("initialMessage"), - callback - ) - } else { - cardSession !!.run(stringOfJSONRPCRequest, callback) - } - } catch (ex: Exception) { - handleException(result, ex) - } - } } class MoshiAdapters { diff --git a/plugin/tangem-sdk-flutter/lib/card_responses/card_response.g.dart b/plugin/tangem-sdk-flutter/lib/card_responses/card_response.g.dart index ea23ef4..e09401d 100644 --- a/plugin/tangem-sdk-flutter/lib/card_responses/card_response.g.dart +++ b/plugin/tangem-sdk-flutter/lib/card_responses/card_response.g.dart @@ -16,7 +16,9 @@ CardResponse _$CardResponseFromJson(Map json) { json['defaultCurve'] as String?, (json['settingsMask'] as List?)?.map((e) => e as String).toList(), json['issuerPublicKey'] as String?, - (json['signingMethods'] as List?)?.map((e) => e as String).toList(), + (json['signingMethods'] as List?) + ?.map((e) => e as String) + .toList(), json['pauseBeforePin2'] as int?, json['walletsCount'] as int?, json['walletIndex'] as int?, @@ -27,14 +29,19 @@ CardResponse _$CardResponseFromJson(Map json) { json['userCounter'] as int?, json['userProtectedCounter'] as int?, json['terminalIsLinked'] as bool, - json['cardData'] == null ? null : CardData.fromJson(json['cardData'] as Map), + json['cardData'] == null + ? null + : CardData.fromJson(json['cardData'] as Map), json['isPin1Default'] as bool?, json['isPin2Default'] as bool?, - (json['wallets'] as List?)?.map((e) => CardWallet.fromJson(e as Map)).toList(), + (json['wallets'] as List?) + ?.map((e) => CardWallet.fromJson(e as Map)) + .toList(), ); } -Map _$CardResponseToJson(CardResponse instance) => { +Map _$CardResponseToJson(CardResponse instance) => + { 'cardId': instance.cardId, 'manufacturerName': instance.manufacturerName, 'status': instance.status, @@ -66,17 +73,21 @@ SignResponse _$SignResponseFromJson(Map json) { ); } -Map _$SignResponseToJson(SignResponse instance) => { +Map _$SignResponseToJson(SignResponse instance) => + { 'signedHashes': instance.signedHashes, }; -DepersonalizeResponse _$DepersonalizeResponseFromJson(Map json) { +DepersonalizeResponse _$DepersonalizeResponseFromJson( + Map json) { return DepersonalizeResponse( json['success'] as bool, ); } -Map _$DepersonalizeResponseToJson(DepersonalizeResponse instance) => { +Map _$DepersonalizeResponseToJson( + DepersonalizeResponse instance) => + { 'success': instance.success, }; @@ -88,7 +99,9 @@ CreateWalletResponse _$CreateWalletResponseFromJson(Map json) { ); } -Map _$CreateWalletResponseToJson(CreateWalletResponse instance) => { +Map _$CreateWalletResponseToJson( + CreateWalletResponse instance) => + { 'cardId': instance.cardId, 'status': instance.status, 'walletPublicKey': instance.walletPublicKey, @@ -101,12 +114,15 @@ PurgeWalletResponse _$PurgeWalletResponseFromJson(Map json) { ); } -Map _$PurgeWalletResponseToJson(PurgeWalletResponse instance) => { +Map _$PurgeWalletResponseToJson( + PurgeWalletResponse instance) => + { 'cardId': instance.cardId, 'status': instance.status, }; -ReadIssuerDataResponse _$ReadIssuerDataResponseFromJson(Map json) { +ReadIssuerDataResponse _$ReadIssuerDataResponseFromJson( + Map json) { return ReadIssuerDataResponse( json['cardId'] as String, json['issuerData'] as String, @@ -115,24 +131,30 @@ ReadIssuerDataResponse _$ReadIssuerDataResponseFromJson(Map jso ); } -Map _$ReadIssuerDataResponseToJson(ReadIssuerDataResponse instance) => { +Map _$ReadIssuerDataResponseToJson( + ReadIssuerDataResponse instance) => + { 'cardId': instance.cardId, 'issuerData': instance.issuerData, 'issuerDataSignature': instance.issuerDataSignature, 'issuerDataCounter': instance.issuerDataCounter, }; -WriteIssuerDataResponse _$WriteIssuerDataResponseFromJson(Map json) { +WriteIssuerDataResponse _$WriteIssuerDataResponseFromJson( + Map json) { return WriteIssuerDataResponse( json['cardId'] as String, ); } -Map _$WriteIssuerDataResponseToJson(WriteIssuerDataResponse instance) => { +Map _$WriteIssuerDataResponseToJson( + WriteIssuerDataResponse instance) => + { 'cardId': instance.cardId, }; -ReadIssuerExDataResponse _$ReadIssuerExDataResponseFromJson(Map json) { +ReadIssuerExDataResponse _$ReadIssuerExDataResponseFromJson( + Map json) { return ReadIssuerExDataResponse( json['cardId'] as String, json['size'] as int, @@ -142,7 +164,9 @@ ReadIssuerExDataResponse _$ReadIssuerExDataResponseFromJson(Map ); } -Map _$ReadIssuerExDataResponseToJson(ReadIssuerExDataResponse instance) => { +Map _$ReadIssuerExDataResponseToJson( + ReadIssuerExDataResponse instance) => + { 'cardId': instance.cardId, 'size': instance.size, 'issuerData': instance.issuerData, @@ -150,13 +174,16 @@ Map _$ReadIssuerExDataResponseToJson(ReadIssuerExDataResponse i 'issuerDataCounter': instance.issuerDataCounter, }; -WriteIssuerExDataResponse _$WriteIssuerExDataResponseFromJson(Map json) { +WriteIssuerExDataResponse _$WriteIssuerExDataResponseFromJson( + Map json) { return WriteIssuerExDataResponse( json['cardId'] as String, ); } -Map _$WriteIssuerExDataResponseToJson(WriteIssuerExDataResponse instance) => { +Map _$WriteIssuerExDataResponseToJson( + WriteIssuerExDataResponse instance) => + { 'cardId': instance.cardId, }; @@ -170,7 +197,9 @@ ReadUserDataResponse _$ReadUserDataResponseFromJson(Map json) { ); } -Map _$ReadUserDataResponseToJson(ReadUserDataResponse instance) => { +Map _$ReadUserDataResponseToJson( + ReadUserDataResponse instance) => + { 'cardId': instance.cardId, 'userData': instance.userData, 'userCounter': instance.userCounter, @@ -178,13 +207,16 @@ Map _$ReadUserDataResponseToJson(ReadUserDataResponse instance) 'userProtectedCounter': instance.userProtectedCounter, }; -WriteUserDataResponse _$WriteUserDataResponseFromJson(Map json) { +WriteUserDataResponse _$WriteUserDataResponseFromJson( + Map json) { return WriteUserDataResponse( json['cardId'] as String, ); } -Map _$WriteUserDataResponseToJson(WriteUserDataResponse instance) => { +Map _$WriteUserDataResponseToJson( + WriteUserDataResponse instance) => + { 'cardId': instance.cardId, }; @@ -195,7 +227,8 @@ SetPinResponse _$SetPinResponseFromJson(Map json) { ); } -Map _$SetPinResponseToJson(SetPinResponse instance) => { +Map _$SetPinResponseToJson(SetPinResponse instance) => + { 'cardId': instance.cardId, 'status': instance.status, }; @@ -207,18 +240,22 @@ WriteFilesResponse _$WriteFilesResponseFromJson(Map json) { ); } -Map _$WriteFilesResponseToJson(WriteFilesResponse instance) => { +Map _$WriteFilesResponseToJson(WriteFilesResponse instance) => + { 'cardId': instance.cardId, 'fileIndices': instance.fileIndices, }; ReadFilesResponse _$ReadFilesResponseFromJson(Map json) { return ReadFilesResponse( - (json['files'] as List).map((e) => FileHex.fromJson(e as Map)).toList(), + (json['files'] as List) + .map((e) => FileHex.fromJson(e as Map)) + .toList(), ); } -Map _$ReadFilesResponseToJson(ReadFilesResponse instance) => { +Map _$ReadFilesResponseToJson(ReadFilesResponse instance) => + { 'files': instance.files, }; @@ -228,16 +265,21 @@ DeleteFilesResponse _$DeleteFilesResponseFromJson(Map json) { ); } -Map _$DeleteFilesResponseToJson(DeleteFilesResponse instance) => { +Map _$DeleteFilesResponseToJson( + DeleteFilesResponse instance) => + { 'cardId': instance.cardId, }; -ChangeFilesSettingsResponse _$ChangeFilesSettingsResponseFromJson(Map json) { +ChangeFilesSettingsResponse _$ChangeFilesSettingsResponseFromJson( + Map json) { return ChangeFilesSettingsResponse( json['cardId'] as String, ); } -Map _$ChangeFilesSettingsResponseToJson(ChangeFilesSettingsResponse instance) => { +Map _$ChangeFilesSettingsResponseToJson( + ChangeFilesSettingsResponse instance) => + { 'cardId': instance.cardId, }; diff --git a/plugin/tangem-sdk-flutter/lib/model/json_rpc.dart b/plugin/tangem-sdk-flutter/lib/model/json_rpc.dart index c729d52..7e89ad7 100644 --- a/plugin/tangem-sdk-flutter/lib/model/json_rpc.dart +++ b/plugin/tangem-sdk-flutter/lib/model/json_rpc.dart @@ -10,92 +10,6 @@ abstract class JSONRPC { final String jsonrpc; JSONRPC(this.id, this.jsonrpc); - - static String? getJsonRpcMethod(String commandType) { - switch (commandType) { - case TangemSdk.cScanCard: - { - return "SCAN_TASK"; - } - case TangemSdk.cSign: - { - return TangemSdk.cSign; - } - case TangemSdk.cPersonalize: - { - return TangemSdk.cPersonalize; - } - case TangemSdk.cDepersonalize: - { - return TangemSdk.cDepersonalize; - } - case TangemSdk.cCreateWallet: - { - return TangemSdk.cCreateWallet; - } - case TangemSdk.cPurgeWallet: - { - return TangemSdk.cPurgeWallet; - } - case TangemSdk.cReadIssuerData: - { - return TangemSdk.cReadIssuerData; - } - case TangemSdk.cWriteIssuerData: - { - return TangemSdk.cWriteIssuerData; - } - case TangemSdk.cReadIssuerExData: - { - return TangemSdk.cReadIssuerExData; - } - case TangemSdk.cWriteIssuerExData: - { - return TangemSdk.cWriteIssuerExData; - } - case TangemSdk.cReadUserData: - { - return TangemSdk.cReadUserData; - } - case TangemSdk.cWriteUserData: - { - return TangemSdk.cWriteUserData; - } - case TangemSdk.cWriteUserProtectedData: - { - return TangemSdk.cWriteUserProtectedData; - } - case TangemSdk.cSetPin1: - { - return TangemSdk.cSetPin1; - } - case TangemSdk.cSetPin2: - { - return TangemSdk.cSetPin2; - } - case TangemSdk.cWriteFiles: - { - return TangemSdk.cWriteFiles; - } - case TangemSdk.cReadFiles: - { - return TangemSdk.cReadFiles; - } - case TangemSdk.cDeleteFiles: - { - return TangemSdk.cDeleteFiles; - } - case TangemSdk.cChangeFilesSettings: - { - return TangemSdk.cChangeFilesSettings; - } - case TangemSdk.cPrepareHashes: - { - return TangemSdk.cPrepareHashes; - } - } - return null; - } } @JsonSerializable() @@ -110,15 +24,28 @@ class JSONRPCRequest extends JSONRPC { factory JSONRPCRequest.fromJson(Map json) => _$JSONRPCRequestFromJson(json); factory JSONRPCRequest.fromCommandDataJson(Map commandDataJson) { - final method = JSONRPC.getJsonRpcMethod(commandDataJson.remove(TangemSdk.commandType)) ?? ""; + final method = TangemSdk.getJsonRpcMethod(commandDataJson.remove(TangemSdk.commandType)) ?? ""; return JSONRPCRequest(method, commandDataJson); } } +@JsonSerializable() +class JSONRPCError { + final int code; + final String message; + final String? data; + + JSONRPCError(this.code, this.message, this.data); + + Map toJson() => _$JSONRPCErrorToJson(this); + + factory JSONRPCError.fromJson(Map json) => _$JSONRPCErrorFromJson(json); +} + @JsonSerializable() class JSONRPCResponse extends JSONRPC { final dynamic result; - final dynamic error; + final JSONRPCError? error; JSONRPCResponse(this.result, this.error, [dynamic id, String jsonrpc = "2.0"]) : super(id, jsonrpc); diff --git a/plugin/tangem-sdk-flutter/lib/model/json_rpc.g.dart b/plugin/tangem-sdk-flutter/lib/model/json_rpc.g.dart index 625cac4..a5a5fe1 100644 --- a/plugin/tangem-sdk-flutter/lib/model/json_rpc.g.dart +++ b/plugin/tangem-sdk-flutter/lib/model/json_rpc.g.dart @@ -25,7 +25,7 @@ Map _$JSONRPCRequestToJson(JSONRPCRequest instance) => json) { return JSONRPCResponse( json['result'], - json['error'], + json['error'] == null ? null : JSONRPCError.fromJson(json['error']), json['id'], json['jsonrpc'] as String, ); @@ -35,5 +35,19 @@ Map _$JSONRPCResponseToJson(JSONRPCResponse instance) => json) { + return JSONRPCError( + json['code'] as int, + json['message'] as String, + json['data'] as String?, + ); +} + +Map _$JSONRPCErrorToJson(JSONRPCError instance) => { + 'code': instance.code, + 'message': instance.message, + 'data': instance.data, }; diff --git a/plugin/tangem-sdk-flutter/lib/plugin_error.dart b/plugin/tangem-sdk-flutter/lib/plugin_error.dart index 2697267..f2f1e5f 100644 --- a/plugin/tangem-sdk-flutter/lib/plugin_error.dart +++ b/plugin/tangem-sdk-flutter/lib/plugin_error.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:flutter/services.dart'; abstract class TangemSdkPluginError implements Exception { @@ -10,6 +8,8 @@ abstract class TangemSdkPluginError implements Exception { String toString() => "${this.runtimeType}. Code: $code, message: $message"; + String toJson() => toString(); + static const int unknownCode = -1; static const int pluginFlutter = 100; static const int pluginKotlin = 1000; @@ -27,10 +27,10 @@ abstract class TangemSdkPluginError implements Exception { case pluginKotlin: return PluginKotlinError(message); default: - return PluginTangemSdkError(code, message); + return PluginTangemSdkError(code, "$message. Detail: ${error.details}"); } } - return PluginUnknownError("", jsonEncode(error)); + return PluginUnknownError(error.toString(), error.toString()); } } diff --git a/plugin/tangem-sdk-flutter/lib/sdk_plugin.dart b/plugin/tangem-sdk-flutter/lib/sdk_plugin.dart index 1091b71..3fa76af 100644 --- a/plugin/tangem-sdk-flutter/lib/sdk_plugin.dart +++ b/plugin/tangem-sdk-flutter/lib/sdk_plugin.dart @@ -7,6 +7,7 @@ import 'package:tangem_sdk/model/command_data.dart'; import 'package:tangem_sdk/model/json_rpc.dart'; import 'package:tangem_sdk/plugin_error.dart'; +import 'extensions/exp_extensions.dart'; import 'model/sdk.dart'; /// Flutter TangemSdk is an interface which provides access to platform specific TangemSdk library. @@ -37,7 +38,7 @@ class TangemSdk { static const cDeleteFiles = 'deleteFiles'; static const cChangeFilesSettings = 'changeFilesSettings'; static const cPrepareHashes = "prepareHashes"; - static const cJsonRpcRequest = 'JSONRPCRequest'; + static const cJsonRpcRequest = 'runJSONRPCRequest'; static const isAllowedOnlyDebugCards = "isAllowedOnlyDebugCards"; static const cardId = "cardId"; @@ -51,7 +52,7 @@ class TangemSdk { @Deprecated("replace by walletPublicKey") static const walletIndex = "walletIndex"; static const walletConfig = "config"; - static const cardConfig = "cardConfig"; + static const cardConfig = "config"; static const issuer = "issuer"; static const manufacturer = "manufacturer"; static const acquirer = "acquirer"; @@ -76,6 +77,32 @@ class TangemSdk { static const jsonRpcRequest = 'JSONRPCRequest'; static const jsonRpcResponse = 'JSONRPCResponse'; + static const _jsonRpcCommands = { + cScanCard: 'SCAN_TASK', + cSign: 'SIGN_COMMAND', + cPersonalize: 'PERSONALIZE_COMMAND', + cDepersonalize: 'DEPERSONALIZE_COMMAND', + cCreateWallet: 'createWallet', + cPurgeWallet: 'purgeWallet', + cReadIssuerData: 'readIssuerData', + cWriteIssuerData: 'writeIssuerData', + cReadIssuerExData: 'readIssuerExData', + cWriteIssuerExData: 'writeIssuerExData', + cReadUserData: 'readUserData', + cWriteUserData: 'writeUserData', + cWriteUserProtectedData: 'writeUserProtectedData', + cSetPin1: 'setPin1', + cSetPin2: 'setPin2', + cWriteFiles: 'writeFiles', + cReadFiles: 'readFiles', + cDeleteFiles: 'deleteFiles', + cChangeFilesSettings: 'changeFilesSettings', + }; + + static String? getJsonRpcMethod(String commandType) { + return _jsonRpcCommands[commandType]; + } + static const MethodChannel _channel = const MethodChannel('tangemSdk'); static const MethodChannel _channelJSONRPC = const MethodChannel('tangemSdk_JSONRPC'); @@ -355,13 +382,22 @@ class TangemSdk { callback.onError(TangemSdkPluginError.createError(error)); } - static Future runJSONRPCRequest(Callback callback, JSONRPCRequest request) async { - final mapWithRequest = {"JSONRPCRequest": jsonEncode(request.toJson())}; + static Future runJSONRPCRequest( + Callback callback, + JSONRPCRequest request, [ + String? cardId, + Message? initialMessage, + ]) async { + final valuesToExport = {TangemSdk.jsonRpcRequest: jsonEncode(request.toJson())}; + cardId?.let((it) => valuesToExport[TangemSdk.cardId] = it); + initialMessage?.let((it) => valuesToExport[TangemSdk.initialMessage] = it.toJson()); + _channelJSONRPC - .invokeMethod(cJsonRpcRequest, mapWithRequest) + .invokeMethod(cJsonRpcRequest, valuesToExport) .then((result) => callback.onSuccess(_createJSONRPCResponse(result))) .catchError((error) => _sendBackError(callback, error)); } + // все ошибки перехваченные этим методом должны прерывать выполнение теста static dynamic _createJSONRPCResponse(dynamic response) { final jsonResponse = jsonDecode(response); @@ -374,4 +410,4 @@ class Callback { final Function(dynamic error) onError; Callback(this.onSuccess, this.onError); -} \ No newline at end of file +} From 17587c5f7e62a7c0e00f03df0eee955dc083ba57 Mon Sep 17 00:00:00 2001 From: Anton Zhilenkov Date: Fri, 25 Jun 2021 20:38:44 +0300 Subject: [PATCH 10/12] AND-1120 added ability to the jsonValue finder to find variables into the arrays. Added test --- .../domain/JsonValueFinder.dart | 83 +++++ .../domain/executable/assert/assert.dart | 2 +- .../domain/executable/step/step_launcher.dart | 4 +- .../domain/variable_service.dart | 112 ++---- test/assets.dart | 272 +++++++++++++++ test/json_value_finder_test.dart | 41 +++ test/variable_service_test.dart | 321 ++++++++++++++++++ 7 files changed, 747 insertions(+), 88 deletions(-) create mode 100644 lib/app_test_launcher/domain/JsonValueFinder.dart create mode 100644 test/assets.dart create mode 100644 test/json_value_finder_test.dart create mode 100644 test/variable_service_test.dart diff --git a/lib/app_test_launcher/domain/JsonValueFinder.dart b/lib/app_test_launcher/domain/JsonValueFinder.dart new file mode 100644 index 0000000..7b61c27 --- /dev/null +++ b/lib/app_test_launcher/domain/JsonValueFinder.dart @@ -0,0 +1,83 @@ +import 'package:tangem_sdk/extensions/exp_extensions.dart'; + +class JsonValueFinder { + final _variablePattern = RegExp("\\{[^\\{\\}]*\\}"); + final _bracketLeft = "{"; + final _bracketRight = "}"; + + final _variables = >{}; + + final String pathDelimiter; + + JsonValueFinder([this.pathDelimiter = "."]); + + void reset() { + _variables.clear(); + } + + void setValue(String name, dynamic value) { + _variables[name] = value; + } + + dynamic removeValue(String name) { + return _variables.remove(name); + } + + dynamic getValue(dynamic pointer) { + return canBeInterpret(pointer) ? getValueFrom(pointer, _variables) : pointer; + } + + dynamic getValueFrom(String pointer, dynamic from) { + if (from == null) return null; + + return _getValueByPattern(removeBrackets(pointer).split(pathDelimiter), 0, from); + } + + dynamic _getValueByPattern(List? pathList, int position, dynamic result) { + if (result == null) return null; + if (pathList == null || position >= pathList.length) return result; + + final key = pathList[position]; + if (result is Map) { + return _getValueByPattern(pathList, ++position, result[key]); + } + + if (result is List) { + if (!key.isNumber()) return null; + + final index = int.parse(key.toString()); + if (index >= result.length) { + return null; + } else { + return _getValueByPattern(pathList, ++position, result[index]); + } + } else { + return result; + } + } + + bool canBeInterpret(dynamic pointer) { + if (pointer == null) { + return false; + } else if (pointer is! String) { + return false; + } else if (!containsVariable(pointer)) { + return false; + } else + return true; + } + + bool containsVariable(String? pointer) { + if (pointer == null || !pointer.contains(_bracketLeft)) return false; + + return _variablePattern.hasMatch(pointer); + } + + String removeBrackets(String text) { + if (text.startsWith(_bracketLeft) && text.endsWith(_bracketRight)) { + return text.substring(1, text.length - 1); + } else { + return text; + } + } +} diff --git a/lib/app_test_launcher/domain/executable/assert/assert.dart b/lib/app_test_launcher/domain/executable/assert/assert.dart index 935523e..72083f3 100644 --- a/lib/app_test_launcher/domain/executable/assert/assert.dart +++ b/lib/app_test_launcher/domain/executable/assert/assert.dart @@ -29,7 +29,7 @@ abstract class TestAssert implements Executable { void run(OnComplete callback); dynamic getFieldValue(String pointer) { - return VariableService.getValue(parentName, pointer); + return VariableService.getStepValue(parentName, pointer); } } diff --git a/lib/app_test_launcher/domain/executable/step/step_launcher.dart b/lib/app_test_launcher/domain/executable/step/step_launcher.dart index 38c5608..7f164a8 100644 --- a/lib/app_test_launcher/domain/executable/step/step_launcher.dart +++ b/lib/app_test_launcher/domain/executable/step/step_launcher.dart @@ -53,10 +53,10 @@ class StepLauncher implements Executable { void _fetchVariables() { _model.params.clear(); _model.rawParams.forEach((key, value) { - _model.params[key] = VariableService.getValue(_model.name, value); + _model.params[key] = VariableService.getStepValue(_model.name, value); }); _model.expectedResult.forEach((key, value) { - _model.expectedResult[key] = VariableService.getValue(_model.name, value); + _model.expectedResult[key] = VariableService.getStepValue(_model.name, value); }); } diff --git a/lib/app_test_launcher/domain/variable_service.dart b/lib/app_test_launcher/domain/variable_service.dart index 1b33ac6..ddd4a7d 100644 --- a/lib/app_test_launcher/domain/variable_service.dart +++ b/lib/app_test_launcher/domain/variable_service.dart @@ -1,24 +1,22 @@ import 'package:devkit/app_test_launcher/domain/common/typedefs.dart'; -import 'package:tangem_sdk/extensions/exp_extensions.dart'; + +import 'JsonValueFinder.dart'; class VariableService { - static final _variablePattern = RegExp("\\{[^\\{\\}]*\\}"); + static final _valueFinder = JsonValueFinder(); + + static final _stepKey = "#"; + static final _parentStepKey = "#parent"; - static final _bracketLeft = "{"; - static final _bracketRight = "}"; - static final _stepPointer = "#"; - static final _parent = "#parent"; static final _actualResult = "actualResult"; static final _error = "error"; - static final _stepValues = {}; - - static void reset() { - _stepValues.clear(); + static void registerStep(String name, SourceMap source) { + _valueFinder.setValue(name, source); } static void registerActualResult(String name, dynamic result) { - final stepMap = _stepValues[name]; + final stepMap = _valueFinder.getValue(name); if (stepMap == null) { // Step is not registered return; @@ -28,7 +26,7 @@ class VariableService { } static void registerError(String name, dynamic result) { - final stepMap = _stepValues[name]; + final stepMap = _valueFinder.getValue(name); if (stepMap == null) { // Step is not registered return; @@ -37,92 +35,36 @@ class VariableService { stepMap[_error] = result; } - static void registerStep(String name, SourceMap source) { - _stepValues[name] = source; - } + static dynamic getStepValue(String name, dynamic pointer) { + if (!_valueFinder.canBeInterpret(pointer)) return null; - static dynamic getValue(String name, dynamic pointer) { - if (pointer == null) { - return null; - } else if (pointer is! String) { - return pointer; - } else if (!_containsVariable(pointer)) { - return pointer; - } else if (_containsStepPointer(pointer)) { - final stepPointer = _extractStepPointer(pointer); + if (_containsPointer(pointer)) { + final stepPointer = _extractPointer(pointer); if (stepPointer == null) return null; - final stepName = stepPointer == _parent ? name : _extractStepName(stepPointer); - final pathValue = _removeBrackets(pointer).replaceAll("$stepPointer.", ""); - final step = _stepValues[stepName]; - return step == null ? null : _getValueByPointer(pathValue, step); + final stepName = stepPointer == _parentStepKey ? name : _extractStepName(stepPointer); + final pathValue = _valueFinder.removeBrackets(pointer).replaceAll("$stepPointer.", ""); + final step = _valueFinder.getValue(stepName); + return step == null ? null : _valueFinder.getValueFrom(pathValue, _valueFinder.getValue("{$step}")); } else { - return _getValueByPointer(pointer, _stepValues[name]); + return _valueFinder.getValueFrom(pointer, _valueFinder.getValue("{$name}")); } } - static dynamic _getValueByPointer(String pointer, dynamic target) { - if (target == null) return null; + static String _extractStepName(String stepPointer) => stepPointer.replaceAll(_stepKey, ""); - return _getValueByPattern(_removeBrackets(pointer).split("\\."), 0, target); - } - - static dynamic _getValueByPattern(List? pointer, int position, dynamic result) { - if (result == null) return null; - if (pointer == null || position >= pointer.length) return result; - - final key = pointer[position]; - if (result is Map) { - return _getValueByPattern(pointer, position + 1, result[key]); - } - - if (result is List) { - if (key.isNumber()) { - final index = int.parse(key.toString()); - if (index >= result.length) { - return null; - } else { - _getValueByPattern(pointer, ++position, result[index]); - } - } else { - final listOfResults = []; - result.forEach((element) { - _getValueByPattern(pointer, position, element)?.let((it) { - listOfResults.add(it); - }); - }); - - return listOfResults.isEmpty ? null : listOfResults; - } - } else { - return result; - } - } - - static bool _containsVariable(String? pointer) { - if (pointer == null || !pointer.contains(_bracketLeft)) return false; - - return _variablePattern.hasMatch(pointer); - } - - static bool _containsStepPointer(String? pointer) { - return pointer == null ? false : pointer.indexOf(_stepPointer) == 1; - } - - static String _extractStepName(String stepPointer) => stepPointer.replaceAll(stepPointer, ""); - - static String? _extractStepPointer(String pointer) => _getPrefix(_removeBrackets(pointer)); + static String? _extractPointer(String pointer) => _getPrefix(_valueFinder.removeBrackets(pointer)); static String? _getPrefix(String value) { final suffixIdx = value.indexOf("."); return suffixIdx < 0 ? null : value.substring(0, suffixIdx); } - static String _removeBrackets(String text) { - if (text.startsWith(_bracketLeft) && text.endsWith(_bracketRight)) { - return text.substring(1, text.length - 1); - } else { - return text; - } + static bool _containsPointer(String? pointer) { + return pointer == null ? false : pointer.indexOf(_stepKey) == 1; + } + + static void reset() { + _valueFinder.reset(); } } diff --git a/test/assets.dart b/test/assets.dart new file mode 100644 index 0000000..d037b95 --- /dev/null +++ b/test/assets.dart @@ -0,0 +1,272 @@ +final json = ''' +{ + "setup": { + "name": "Twins", + "description": "Two test iterations with two steps iterations", + "personalizationConfig": { + "config": { + "issuerName": "TANGEM SDK", + "acquirerName": "Smart Cash", + "series": "BB", + "startNumber": 300000000000, + "count": 0, + "pin": "000000", + "pin2": "000", + "pin3": "", + "hexCrExKey": "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF", + "cvc": "000", + "pauseBeforePin2": 5000, + "smartSecurityDelay": true, + "curveID": "Secp256k1", + "signingMethods": [ + "SignHash" + ], + "maxSignatures": 999999, + "isReusable": true, + "allowSetPIN1": true, + "allowSetPIN2": true, + "useActivation": false, + "useCvc": false, + "useNDEF": true, + "useDynamicNDEF": true, + "useOneCommandAtTime": false, + "useBlock": false, + "allowSelectBlockchain": true, + "prohibitPurgeWallet": false, + "allowUnencrypted": true, + "allowFastEncryption": true, + "protectIssuerDataAgainstReplay": false, + "prohibitDefaultPIN1": false, + "disablePrecomputedNDEF": false, + "skipSecurityDelayIfValidatedByIssuer": true, + "skipCheckPIN2CVCIfValidatedByIssuer": true, + "skipSecurityDelayIfValidatedByLinkedTerminal": true, + "restrictOverwriteIssuerExtraData": false, + "requireTerminalTxSignature": false, + "requireTerminalCertSignature": false, + "checkPIN3OnCard": false, + "createWallet": true, + "walletsCount": 1, + "cardData": { + "batchId": "FFFF", + "blockchainName": "ETH", + "issuerName": "TANGEM SDK", + "manufacturerSignature": null, + "manufactureDateTime": "2021-06-22", + "productMask": [ + "Note" + ] + }, + "ndefRecords": [ + { + "type": "AAR", + "value": "com.tangem.wallet" + }, + { + "type": "URI", + "value": "https://tangem.com" + } + ] + }, + "issuer": { + "name": "TANGEM SDK", + "id": "TANGEM SDK", + "dataKeyPair": { + "publicKey": "045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26", + "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92" + }, + "transactionKeyPair": { + "publicKey": "0484c5192e9bfa6c528a344f442137a92b89ea835bfef1d04cb4362eb906b508c5889846cfea71ba6dc7b3120c2208df9c46127d3d85cb5cfbd1479e97133a39d8", + "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405081918171615141312" + } + }, + "acquirer": { + "name": "Smart Cash", + "id": "Smart Cash", + "keyPair": { + "publicKey": "0456ad1a82b22bcb40c38fd08939f87e6b80e40dec5b3bdb351c55fcd709e47f9fb2ed00c2304d3a986f79c5ae0ac3c84e88da46dc8f513b7542c716af8c9a2daf", + "privateKey": "21222324252627284771ED81F2BACF57479E4735EB1405083927372D40DA9E92" + } + }, + "manufacturer": { + "name": "Tangem", + "keyPair": { + "publicKey": "04bab86d56298c996f564a84fc88e28aed38184b12f07e519113bef48c76f3df3adc303599b08ac05b55ec3df98d9338573a6242f76f5d28f4f0f364e87e8fca2f", + "privateKey": "1b48cfd24bbb5b394771ed81f2bacf57479e4735eb1405083927372d40da9e92" + } + } + }, + "sdkConfig": {}, + "minimalFirmware": null, + "platform": null, + "iterations": 2, + "creationDateMs": 1624407062610 + }, + "steps": [ + { + "name": "Scan card 1", + "method": "SCAN_TASK", + "parameters": {}, + "expectedResult": { + "cardId": "BB03000000000004", + "manufacturerName": "TANGEM", + "status": "Loaded", + "firmwareVersion": { + "version": "3.37d SDK" + }, + "cardPublicKey": "0497C0424AF7BF8CE9920CB90EDAC2010FDED904937BC6D1D6E82E9254E0EFE4D4FDF355BB16BD0E9550D8BE5AB741B897FBC70360E99A86B97BA7DCC2FBF77D7E", + "defaultCurve": "Secp256k1", + "settingsMask": [ + "IsReusable", + "AllowSetPIN1", + "AllowSetPIN2", + "UseNDEF", + "UseDynamicNDEF", + "SmartSecurityDelay", + "AllowUnencrypted", + "AllowFastEncryption", + "AllowSelectBlockchain", + "SkipSecurityDelayIfValidatedByLinkedTerminal", + "SkipCheckPIN2CVCIfValidatedByIssuer", + "SkipSecurityDelayIfValidatedByIssuer" + ], + "issuerPublicKey": "045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26", + "signingMethods": [ + "SignHash" + ], + "pauseBeforePin2": 500, + "walletsCount": null, + "walletIndex": null, + "health": 0, + "isActivated": false, + "activationSeed": null, + "paymentFlowVersion": null, + "userCounter": null, + "userProtectedCounter": null, + "terminalIsLinked": false, + "cardData": { + "batchId": "FFFF", + "blockchainName": "ETH", + "issuerName": "TANGEM SDK", + "manufacturerSignature": "7CBEABA11F9D564A244260AC15CE0A26AB885811322C38B9FCA9E55EC7654C2027219ABB1BEAD190C2C370F48635129F76E8423FA8003DB6A08D2F64B1574004", + "manufactureDateTime": "2021-06-23", + "productMask": [ + "Note" + ] + }, + "isPin1Default": true, + "isPin2Default": true, + "wallets": [ + { + "index": 0, + "status": "Loaded", + "curve": "Secp256k1", + "settingsMask": [ + "IsReusable", + "AllowSetPIN1", + "AllowSetPIN2", + "UseNDEF", + "UseDynamicNDEF", + "SmartSecurityDelay", + "AllowUnencrypted", + "AllowFastEncryption", + "AllowSelectBlockchain", + "SkipSecurityDelayIfValidatedByLinkedTerminal", + "SkipCheckPIN2CVCIfValidatedByIssuer", + "SkipSecurityDelayIfValidatedByIssuer" + ], + "publicKey": "0408C8EF8DEC3B9C910B44D8A5C72138A333365ED2DC34E91FACA8B9A1B37EB5010CB0080E8144807998F50F6BE4B1CA4BF140ADFCEF34124D1417DD116535FB2D", + "signedHashes": 0, + "remainingSignatures": 999999 + } + ] + }, + "asserts": [], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 2 + }, + { + "name": "Scan card 2", + "method": "SCAN_TASK", + "parameters": {}, + "expectedResult": { + "cardId": "BB03000000000005", + "manufacturerName": "TANGEM", + "status": "Loaded", + "firmwareVersion": { + "version": "3.37d SDK" + }, + "cardPublicKey": "0497C0424AF7BF8CE9920CB90EDAC2010FDED904937BC6D1D6E82E9254E0EFE4D4FDF355BB16BD0E9550D8BE5AB741B897FBC70360E99A86B97BA7DCC2FBF77D7E", + "defaultCurve": "Secp256k1", + "settingsMask": [ + "IsReusable", + "AllowSetPIN1", + "AllowSetPIN2", + "UseNDEF", + "UseDynamicNDEF", + "SmartSecurityDelay", + "AllowUnencrypted", + "AllowFastEncryption", + "AllowSelectBlockchain", + "SkipSecurityDelayIfValidatedByLinkedTerminal", + "SkipCheckPIN2CVCIfValidatedByIssuer", + "SkipSecurityDelayIfValidatedByIssuer" + ], + "issuerPublicKey": "045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26", + "signingMethods": [ + "SignHash" + ], + "pauseBeforePin2": 500, + "walletsCount": null, + "walletIndex": null, + "health": 0, + "isActivated": false, + "activationSeed": null, + "paymentFlowVersion": null, + "userCounter": null, + "userProtectedCounter": null, + "terminalIsLinked": false, + "cardData": { + "batchId": "FFFF", + "blockchainName": "ETH", + "issuerName": "TANGEM SDK", + "manufacturerSignature": "7CBEABA11F9D564A244260AC15CE0A26AB885811322C38B9FCA9E55EC7654C2027219ABB1BEAD190C2C370F48635129F76E8423FA8003DB6A08D2F64B1574004", + "manufactureDateTime": "2021-06-23", + "productMask": [ + "Note" + ] + }, + "isPin1Default": true, + "isPin2Default": true, + "wallets": [ + { + "index": 0, + "status": "Loaded", + "curve": "Secp256k1", + "settingsMask": [ + "IsReusable", + "AllowSetPIN1", + "AllowSetPIN2", + "UseNDEF", + "UseDynamicNDEF", + "SmartSecurityDelay", + "AllowUnencrypted", + "AllowFastEncryption", + "AllowSelectBlockchain", + "SkipSecurityDelayIfValidatedByLinkedTerminal", + "SkipCheckPIN2CVCIfValidatedByIssuer", + "SkipSecurityDelayIfValidatedByIssuer" + ], + "publicKey": "0408C8EF8DEC3B9C910B44D8A5C72138A333365ED2DC34E91FACA8B9A1B37EB5010CB0080E8144807998F50F6BE4B1CA4BF140ADFCEF34124D1417DD116535FB2D", + "signedHashes": 0, + "remainingSignatures": 999999 + } + ] + }, + "asserts": [], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 2 + } + ] +} +'''; \ No newline at end of file diff --git a/test/json_value_finder_test.dart b/test/json_value_finder_test.dart new file mode 100644 index 0000000..5aa6c5c --- /dev/null +++ b/test/json_value_finder_test.dart @@ -0,0 +1,41 @@ +import 'dart:convert'; + +import 'package:devkit/app_test_launcher/domain/JsonValueFinder.dart'; +import 'package:test/test.dart'; + +import 'assets.dart'; + +void main() { + group("JsonValueFinder", () { + test("Find a variable by a simple variable path", findBySimplePath); + test("Find a variable by by an array element and into the element", findByArrayElement); + }); +} + +void findBySimplePath() { + final map = jsonDecode(json); + final finder = JsonValueFinder(); + finder.setValue("test", map); + dynamic resultValue = finder.getValue("{test.setup.name}"); + expect(resultValue, "Twins"); + resultValue = finder.getValue("{test.setup.iterations}"); + expect(resultValue, 2); + resultValue = finder.getValue("{test.setup.personalizationConfig.config.pin}"); + expect(resultValue, "000000"); + resultValue = finder.getValue("{test.setup.personalizationConfig.config.p}"); + expect(resultValue, null); +} + +void findByArrayElement() { + final map = jsonDecode(json); + final finder = JsonValueFinder(); + finder.setValue("test", map); + dynamic resultValue = finder.getValue("{test.setup.personalizationConfig.config.signingMethods.0}"); + expect(resultValue, "SignHash"); + resultValue = finder.getValue("{test.setup.personalizationConfig.config.ndefRecords.0.type}"); + expect(resultValue, "AAR"); + resultValue = finder.getValue("{test.setup.personalizationConfig.config.signingMethods.1}"); + expect(resultValue, null); + resultValue = finder.getValue("{test.setup.personalizationConfig.config.signingMethods.qwe}"); + expect(resultValue, null); +} diff --git a/test/variable_service_test.dart b/test/variable_service_test.dart new file mode 100644 index 0000000..2cd714d --- /dev/null +++ b/test/variable_service_test.dart @@ -0,0 +1,321 @@ +import 'dart:convert'; + +import 'package:devkit/app_test_launcher/domain/variable_service.dart'; +import 'package:test/test.dart'; + +final _stepName1 = "Scan card 1"; +final _stepName2 = "Scan card 2"; + +void main() { + group("VariableService", () { + prepareTestForVariableService(); + test("Find the variable into the step", findInStep); + test("Find the variable from another step", findFromAnotherStep); + test("Find the variable by parent", findByParent); + }); +} + +void prepareTestForVariableService() { + final map = jsonDecode(json); + VariableService.registerStep(_stepName1, (map["steps"] as List)[0]); + VariableService.registerStep(_stepName2, (map["steps"] as List)[1]); +} + +void findInStep() { + dynamic resultValue = VariableService.getStepValue(_stepName1, "{expectedResult.cardId}"); + expect(resultValue, "BB03000000000004"); + resultValue = VariableService.getStepValue(_stepName1, "{#$_stepName1.expectedResult.cardId}"); + expect(resultValue, "BB03000000000004"); + resultValue = VariableService.getStepValue(_stepName2, "{#$_stepName2.expectedResult.cardId}"); + expect(resultValue, "BB03000000000005"); +} + +void findFromAnotherStep() { + dynamic resultValue = VariableService.getStepValue("", "{#$_stepName1.expectedResult.cardId}"); + expect(resultValue, "BB03000000000004"); + resultValue = VariableService.getStepValue("any", "{#$_stepName1.expectedResult.cardId}"); + expect(resultValue, "BB03000000000004"); + resultValue = VariableService.getStepValue(_stepName2, "{#$_stepName1.expectedResult.cardId}"); + expect(resultValue, "BB03000000000004"); +} + +void findByParent() { + // no access to the #parent key from the empty step + dynamic resultValue = VariableService.getStepValue(_stepName1, "{#parent.expectedResult.cardId}"); + expect(resultValue, "BB03000000000004"); + resultValue = VariableService.getStepValue("", "{#parent.expectedResult.cardId}"); + expect(resultValue, null); +} + +final json = ''' +{ + "setup": { + "name": "Twins", + "description": "Two test iterations with two steps iterations", + "personalizationConfig": { + "config": { + "issuerName": "TANGEM SDK", + "acquirerName": "Smart Cash", + "series": "BB", + "startNumber": 300000000000, + "count": 0, + "pin": "000000", + "pin2": "000", + "pin3": "", + "hexCrExKey": "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF", + "cvc": "000", + "pauseBeforePin2": 5000, + "smartSecurityDelay": true, + "curveID": "Secp256k1", + "signingMethods": [ + "SignHash" + ], + "maxSignatures": 999999, + "isReusable": true, + "allowSetPIN1": true, + "allowSetPIN2": true, + "useActivation": false, + "useCvc": false, + "useNDEF": true, + "useDynamicNDEF": true, + "useOneCommandAtTime": false, + "useBlock": false, + "allowSelectBlockchain": true, + "prohibitPurgeWallet": false, + "allowUnencrypted": true, + "allowFastEncryption": true, + "protectIssuerDataAgainstReplay": false, + "prohibitDefaultPIN1": false, + "disablePrecomputedNDEF": false, + "skipSecurityDelayIfValidatedByIssuer": true, + "skipCheckPIN2CVCIfValidatedByIssuer": true, + "skipSecurityDelayIfValidatedByLinkedTerminal": true, + "restrictOverwriteIssuerExtraData": false, + "requireTerminalTxSignature": false, + "requireTerminalCertSignature": false, + "checkPIN3OnCard": false, + "createWallet": true, + "walletsCount": 1, + "cardData": { + "batchId": "FFFF", + "blockchainName": "ETH", + "issuerName": "TANGEM SDK", + "manufacturerSignature": null, + "manufactureDateTime": "2021-06-22", + "productMask": [ + "Note" + ] + }, + "ndefRecords": [ + { + "type": "AAR", + "value": "com.tangem.wallet" + }, + { + "type": "URI", + "value": "https://tangem.com" + } + ] + }, + "issuer": { + "name": "TANGEM SDK", + "id": "TANGEM SDK", + "dataKeyPair": { + "publicKey": "045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26", + "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92" + }, + "transactionKeyPair": { + "publicKey": "0484c5192e9bfa6c528a344f442137a92b89ea835bfef1d04cb4362eb906b508c5889846cfea71ba6dc7b3120c2208df9c46127d3d85cb5cfbd1479e97133a39d8", + "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405081918171615141312" + } + }, + "acquirer": { + "name": "Smart Cash", + "id": "Smart Cash", + "keyPair": { + "publicKey": "0456ad1a82b22bcb40c38fd08939f87e6b80e40dec5b3bdb351c55fcd709e47f9fb2ed00c2304d3a986f79c5ae0ac3c84e88da46dc8f513b7542c716af8c9a2daf", + "privateKey": "21222324252627284771ED81F2BACF57479E4735EB1405083927372D40DA9E92" + } + }, + "manufacturer": { + "name": "Tangem", + "keyPair": { + "publicKey": "04bab86d56298c996f564a84fc88e28aed38184b12f07e519113bef48c76f3df3adc303599b08ac05b55ec3df98d9338573a6242f76f5d28f4f0f364e87e8fca2f", + "privateKey": "1b48cfd24bbb5b394771ed81f2bacf57479e4735eb1405083927372d40da9e92" + } + } + }, + "sdkConfig": {}, + "minimalFirmware": null, + "platform": null, + "iterations": 2, + "creationDateMs": 1624407062610 + }, + "steps": [ + { + "name": "Scan card 1", + "method": "SCAN_TASK", + "parameters": {}, + "expectedResult": { + "cardId": "BB03000000000004", + "manufacturerName": "TANGEM", + "status": "Loaded", + "firmwareVersion": { + "version": "3.37d SDK" + }, + "cardPublicKey": "0497C0424AF7BF8CE9920CB90EDAC2010FDED904937BC6D1D6E82E9254E0EFE4D4FDF355BB16BD0E9550D8BE5AB741B897FBC70360E99A86B97BA7DCC2FBF77D7E", + "defaultCurve": "Secp256k1", + "settingsMask": [ + "IsReusable", + "AllowSetPIN1", + "AllowSetPIN2", + "UseNDEF", + "UseDynamicNDEF", + "SmartSecurityDelay", + "AllowUnencrypted", + "AllowFastEncryption", + "AllowSelectBlockchain", + "SkipSecurityDelayIfValidatedByLinkedTerminal", + "SkipCheckPIN2CVCIfValidatedByIssuer", + "SkipSecurityDelayIfValidatedByIssuer" + ], + "issuerPublicKey": "045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26", + "signingMethods": [ + "SignHash" + ], + "pauseBeforePin2": 500, + "walletsCount": null, + "walletIndex": null, + "health": 0, + "isActivated": false, + "activationSeed": null, + "paymentFlowVersion": null, + "userCounter": null, + "userProtectedCounter": null, + "terminalIsLinked": false, + "cardData": { + "batchId": "FFFF", + "blockchainName": "ETH", + "issuerName": "TANGEM SDK", + "manufacturerSignature": "7CBEABA11F9D564A244260AC15CE0A26AB885811322C38B9FCA9E55EC7654C2027219ABB1BEAD190C2C370F48635129F76E8423FA8003DB6A08D2F64B1574004", + "manufactureDateTime": "2021-06-23", + "productMask": [ + "Note" + ] + }, + "isPin1Default": true, + "isPin2Default": true, + "wallets": [ + { + "index": 0, + "status": "Loaded", + "curve": "Secp256k1", + "settingsMask": [ + "IsReusable", + "AllowSetPIN1", + "AllowSetPIN2", + "UseNDEF", + "UseDynamicNDEF", + "SmartSecurityDelay", + "AllowUnencrypted", + "AllowFastEncryption", + "AllowSelectBlockchain", + "SkipSecurityDelayIfValidatedByLinkedTerminal", + "SkipCheckPIN2CVCIfValidatedByIssuer", + "SkipSecurityDelayIfValidatedByIssuer" + ], + "publicKey": "0408C8EF8DEC3B9C910B44D8A5C72138A333365ED2DC34E91FACA8B9A1B37EB5010CB0080E8144807998F50F6BE4B1CA4BF140ADFCEF34124D1417DD116535FB2D", + "signedHashes": 0, + "remainingSignatures": 999999 + } + ] + }, + "asserts": [], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 2 + }, + { + "name": "Scan card 2", + "method": "SCAN_TASK", + "parameters": {}, + "expectedResult": { + "cardId": "BB03000000000005", + "manufacturerName": "TANGEM", + "status": "Loaded", + "firmwareVersion": { + "version": "3.37d SDK" + }, + "cardPublicKey": "0497C0424AF7BF8CE9920CB90EDAC2010FDED904937BC6D1D6E82E9254E0EFE4D4FDF355BB16BD0E9550D8BE5AB741B897FBC70360E99A86B97BA7DCC2FBF77D7E", + "defaultCurve": "Secp256k1", + "settingsMask": [ + "IsReusable", + "AllowSetPIN1", + "AllowSetPIN2", + "UseNDEF", + "UseDynamicNDEF", + "SmartSecurityDelay", + "AllowUnencrypted", + "AllowFastEncryption", + "AllowSelectBlockchain", + "SkipSecurityDelayIfValidatedByLinkedTerminal", + "SkipCheckPIN2CVCIfValidatedByIssuer", + "SkipSecurityDelayIfValidatedByIssuer" + ], + "issuerPublicKey": "045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26", + "signingMethods": [ + "SignHash" + ], + "pauseBeforePin2": 500, + "walletsCount": null, + "walletIndex": null, + "health": 0, + "isActivated": false, + "activationSeed": null, + "paymentFlowVersion": null, + "userCounter": null, + "userProtectedCounter": null, + "terminalIsLinked": false, + "cardData": { + "batchId": "FFFF", + "blockchainName": "ETH", + "issuerName": "TANGEM SDK", + "manufacturerSignature": "7CBEABA11F9D564A244260AC15CE0A26AB885811322C38B9FCA9E55EC7654C2027219ABB1BEAD190C2C370F48635129F76E8423FA8003DB6A08D2F64B1574004", + "manufactureDateTime": "2021-06-23", + "productMask": [ + "Note" + ] + }, + "isPin1Default": true, + "isPin2Default": true, + "wallets": [ + { + "index": 0, + "status": "Loaded", + "curve": "Secp256k1", + "settingsMask": [ + "IsReusable", + "AllowSetPIN1", + "AllowSetPIN2", + "UseNDEF", + "UseDynamicNDEF", + "SmartSecurityDelay", + "AllowUnencrypted", + "AllowFastEncryption", + "AllowSelectBlockchain", + "SkipSecurityDelayIfValidatedByLinkedTerminal", + "SkipCheckPIN2CVCIfValidatedByIssuer", + "SkipSecurityDelayIfValidatedByIssuer" + ], + "publicKey": "0408C8EF8DEC3B9C910B44D8A5C72138A333365ED2DC34E91FACA8B9A1B37EB5010CB0080E8144807998F50F6BE4B1CA4BF140ADFCEF34124D1417DD116535FB2D", + "signedHashes": 0, + "remainingSignatures": 999999 + } + ] + }, + "asserts": [], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 2 + } + ] +} +'''; From 5a53ced7a9b2a083eeadfd799b7e3a1c716ac235 Mon Sep 17 00:00:00 2001 From: Alexander Osokin Date: Tue, 29 Jun 2021 18:19:33 +0300 Subject: [PATCH 11/12] IOS-896 Dev kit ios recreate and prepare for the new sdk --- ios/.gitignore | 1 + ios/Flutter/.last_build_id | 1 - ios/Flutter/Debug.xcconfig | 2 +- ios/Flutter/Release.xcconfig | 2 +- ios/Podfile | 89 +-- ios/Podfile.lock | 99 +-- .../project.pbxproj | 177 ++--- .../contents.xcworkspacedata | 2 +- .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../WorkspaceSettings.xcsettings} | 4 +- .../xcshareddata/xcschemes/Runner.xcscheme} | 34 +- .../contents.xcworkspacedata | 2 +- .../xcshareddata/WorkspaceSettings.xcsettings | 8 + ios/Runner/Runner-Bridging-Header.h | 2 +- .../contents.xcworkspacedata | 10 - .../ios/Classes/SwiftTangemSdkPlugin.swift | 693 +++++++++--------- .../tangem-sdk-flutter/ios/tangem_sdk.podspec | 4 +- 17 files changed, 505 insertions(+), 625 deletions(-) delete mode 100644 ios/Flutter/.last_build_id rename ios/{Tangem DevKit.xcodeproj => Runner.xcodeproj}/project.pbxproj (79%) rename ios/{Tangem DevKit.xcodeproj => Runner.xcodeproj}/project.xcworkspace/contents.xcworkspacedata (71%) rename ios/{Tangem DevKit.xcodeproj => Runner.xcodeproj}/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename ios/{Tangem DevKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist => Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings} (78%) rename ios/{Tangem DevKit.xcodeproj/xcshareddata/xcschemes/Tangem DevKit.xcscheme => Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme} (78%) create mode 100644 ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 ios/Tangem DevKit.xcworkspace/contents.xcworkspacedata diff --git a/ios/.gitignore b/ios/.gitignore index e96ef60..151026b 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -18,6 +18,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/ios/Flutter/.last_build_id b/ios/Flutter/.last_build_id deleted file mode 100644 index acbe8b3..0000000 --- a/ios/Flutter/.last_build_id +++ /dev/null @@ -1 +0,0 @@ -28141c607f8a167c562e88ee9f9fc15d \ No newline at end of file diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index be08867..ec97fc6 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1,2 +1,2 @@ -#include "Pods/Target Support Files/Pods-Tangem DevKit/Pods-Tangem DevKit.debug.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 29cbe91..c4855bf 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1,2 +1,2 @@ -#include "Pods/Target Support Files/Pods-Tangem DevKit/Pods-Tangem DevKit.release.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile index 57274ff..4cc8643 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,94 +1,43 @@ # Uncomment this line to define a global platform for your project -platform :ios, '13.0' +# platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' -project 'Tangem DevKit', { +project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } -def parse_KV_file(file, separator='=') - file_abs_path = File.expand_path(file) - if !File.exists? file_abs_path - return []; +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end - generated_key_values = {} - skip_line_start_symbols = ["#", "/"] - File.foreach(file_abs_path) do |line| - next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } - plugin = line.split(pattern=separator) - if plugin.length == 2 - podname = plugin[0].strip() - path = plugin[1].strip() - podpath = File.expand_path("#{path}", file_abs_path) - generated_key_values[podname] = podpath - else - puts "Invalid plugin specification: #{line}" - end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches end - generated_key_values + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end -target 'Tangem DevKit' do - use_frameworks! - use_modular_headers! - - # Flutter Pod - - copied_flutter_dir = File.join(__dir__, 'Flutter') - copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') - copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') - unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) - # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. - # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. - # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. - - generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') - unless File.exist?(generated_xcode_build_settings_path) - raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) - cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - unless File.exist?(copied_framework_path) - FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) - end - unless File.exist?(copied_podspec_path) - FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) - end - end - - # Keep pod path relative so it can be checked into Podfile.lock. - pod 'Flutter', :path => 'Flutter' +flutter_ios_podfile_setup - # Plugin Pods +target 'Runner' do + use_frameworks! + use_modular_headers! - # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock - # referring to absolute paths on developers' machines. - system('rm -rf .symlinks') - system('mkdir -p .symlinks/plugins') - plugin_pods = parse_KV_file('../.flutter-plugins') - plugin_pods.each do |name, path| - symlink = File.join('.symlinks', 'plugins', name) - File.symlink(path, symlink) - pod name, :path => File.join(symlink, 'ios') - end + pod 'TangemSdk', :path => '../../tangem-sdk-ios' - pod 'TangemSdk' - #pod 'TangemSdk', :path => '../../card-sdk-swift' + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end -# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. -install! 'cocoapods', :disable_input_output_paths => true - post_install do |installer| installer.pods_project.targets.each do |target| - target.build_configurations.each do |config| - config.build_settings['ENABLE_BITCODE'] = 'NO' - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' - end + flutter_additional_ios_build_settings(target) end end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 65d466e..0f2122d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -8,6 +8,7 @@ PODS: - SDWebImageWebPCoder - fluttertoast (0.0.2): - Flutter + - Toast - libwebp (1.2.0): - libwebp/demux (= 1.2.0) - libwebp/mux (= 1.2.0) @@ -20,64 +21,32 @@ PODS: - Mantle (2.1.6): - Mantle/extobjc (= 2.1.6) - Mantle/extobjc (2.1.6) - - native_device_orientation (0.0.1): - - Flutter - path_provider (0.0.1): - Flutter - - path_provider_linux (0.0.1): - - Flutter - - path_provider_macos (0.0.1): - - Flutter - - path_provider_windows (0.0.1): - - Flutter - - SDWebImage/Core (5.10.4) - - SDWebImageWebPCoder (0.8.3): + - SDWebImage/Core (5.11.1) + - SDWebImageWebPCoder (0.8.4): - libwebp (~> 1.0) - SDWebImage/Core (~> 5.10) - share (0.0.1): - Flutter - shared_preferences (0.0.1): - Flutter - - shared_preferences_linux (0.0.1): - - Flutter - - shared_preferences_macos (0.0.1): - - Flutter - - shared_preferences_web (0.0.1): - - Flutter - - shared_preferences_windows (0.0.1): - - Flutter - tangem_sdk (0.0.3): - Flutter - - TangemSdk (~> 2.4.2) - - TangemSdk (2.4.2) - - video_player (0.0.1): - - Flutter - - video_player_web (0.0.1): - - Flutter - - wakelock (0.0.1): - - Flutter + - TangemSdk (~> 3.0.2) + - TangemSdk (3.0.2) + - Toast (4.0.0) DEPENDENCIES: - camera (from `.symlinks/plugins/camera/ios`) - Flutter (from `Flutter`) - flutter_image_compress (from `.symlinks/plugins/flutter_image_compress/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - - native_device_orientation (from `.symlinks/plugins/native_device_orientation/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) - - path_provider_linux (from `.symlinks/plugins/path_provider_linux/ios`) - - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) - - path_provider_windows (from `.symlinks/plugins/path_provider_windows/ios`) - share (from `.symlinks/plugins/share/ios`) - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - - shared_preferences_linux (from `.symlinks/plugins/shared_preferences_linux/ios`) - - shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`) - - shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`) - - shared_preferences_windows (from `.symlinks/plugins/shared_preferences_windows/ios`) - tangem_sdk (from `.symlinks/plugins/tangem_sdk/ios`) - - TangemSdk - - video_player (from `.symlinks/plugins/video_player/ios`) - - video_player_web (from `.symlinks/plugins/video_player_web/ios`) - - wakelock (from `.symlinks/plugins/wakelock/ios`) + - TangemSdk (from `../../tangem-sdk-ios`) SPEC REPOS: trunk: @@ -85,7 +54,7 @@ SPEC REPOS: - Mantle - SDWebImage - SDWebImageWebPCoder - - TangemSdk + - Toast EXTERNAL SOURCES: camera: @@ -96,63 +65,33 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_image_compress/ios" fluttertoast: :path: ".symlinks/plugins/fluttertoast/ios" - native_device_orientation: - :path: ".symlinks/plugins/native_device_orientation/ios" path_provider: :path: ".symlinks/plugins/path_provider/ios" - path_provider_linux: - :path: ".symlinks/plugins/path_provider_linux/ios" - path_provider_macos: - :path: ".symlinks/plugins/path_provider_macos/ios" - path_provider_windows: - :path: ".symlinks/plugins/path_provider_windows/ios" share: :path: ".symlinks/plugins/share/ios" shared_preferences: :path: ".symlinks/plugins/shared_preferences/ios" - shared_preferences_linux: - :path: ".symlinks/plugins/shared_preferences_linux/ios" - shared_preferences_macos: - :path: ".symlinks/plugins/shared_preferences_macos/ios" - shared_preferences_web: - :path: ".symlinks/plugins/shared_preferences_web/ios" - shared_preferences_windows: - :path: ".symlinks/plugins/shared_preferences_windows/ios" tangem_sdk: :path: ".symlinks/plugins/tangem_sdk/ios" - video_player: - :path: ".symlinks/plugins/video_player/ios" - video_player_web: - :path: ".symlinks/plugins/video_player_web/ios" - wakelock: - :path: ".symlinks/plugins/wakelock/ios" + TangemSdk: + :path: "../../tangem-sdk-ios" SPEC CHECKSUMS: - camera: a0ca5080336f7af47b88436e5e26da3dee5568f0 - Flutter: 0e3d915762c693b495b44d77113d4970485de6ec + camera: 3164201dc344383e62282964016528c4f5a9ad50 + Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c flutter_image_compress: 082f8daaf6c1b0c9fe798251c750ef0ecd98d7ae - fluttertoast: b644586ef3b16f67fae9a1f8754cef6b2d6b634b + fluttertoast: 6122fa75143e992b1d3470f61000f591a798cc58 libwebp: e90b9c01d99205d03b6bb8f2c8c415e5a4ef66f0 Mantle: 4c0ed6ce47c96eccc4dc3bb071deb3def0e2c3be - native_device_orientation: e24d00be281de72996640885d80e706142707660 path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c - path_provider_linux: 4d630dc393e1f20364f3e3b4a2ff41d9674a84e4 - path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 - path_provider_windows: a2b81600c677ac1959367280991971cb9a1edb3b - SDWebImage: c666b97e1fa9c64b4909816a903322018f0a9c84 - SDWebImageWebPCoder: bbf46e29fb8d1980a78ad3d5e9b4123c77f10ebc + SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d + SDWebImageWebPCoder: f93010f3f6c031e2f8fb3081ca4ee6966c539815 share: 0b2c3e82132f5888bccca3351c504d0003b3b410 shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d - shared_preferences_linux: afefbfe8d921e207f01ede8b60373d9e3b566b78 - shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087 - shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 - shared_preferences_windows: 36b76d6f54e76ead957e60b49e2f124b4cd3e6ae - tangem_sdk: e7063c3a57970265a09d435fc3f533286efddb6d - TangemSdk: 468a619db3a2105b2f93524893bc7aa5de0647c3 - video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e - video_player_web: da8cadb8274ed4f8dbee8d7171b420dedd437ce7 - wakelock: 0d4a70faf8950410735e3f61fb15d517c8a6efc4 + tangem_sdk: 37d2c964859b56040342eb01cb3d2c35e4e8f2fb + TangemSdk: 26b8d99c941d307ce44c2eb0bb73d5337ced5bed + Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 -PODFILE CHECKSUM: c158dd0976c7bc7fa3d9cfd2d0fb213ba376cb3e +PODFILE CHECKSUM: b3ccbb53d4ae4e225daec72aebe6f42015aaa139 COCOAPODS: 1.10.1 diff --git a/ios/Tangem DevKit.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj similarity index 79% rename from ios/Tangem DevKit.xcodeproj/project.pbxproj rename to ios/Runner.xcodeproj/project.pbxproj index 41d5657..63b255b 100644 --- a/ios/Tangem DevKit.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,18 +3,17 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 5D3F3CFC25CAA8BC00BA3BC8 /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D3F3CFB25CAA8BC00BA3BC8 /* App.framework */; }; + 4E7BA296778EAA717D7B3614 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C61C09B78255B9DC5704B4C5 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - FE8E94330493F527E1283A8C /* Pods_Tangem_DevKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A54215D1256FD8320E9A7CE2 /* Pods_Tangem_DevKit.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -33,26 +32,22 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 211F0E5BAD43410CA55ECEE2 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 274A521632525F3F9DB03A17 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 5D3F3CFB25CAA8BC00BA3BC8 /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; - 5DE841A3246D272300AE6931 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; - 66D8E5CB1DF2E9A73602813A /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 3FA1D002D52E3F5FDF1BC105 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 5DE3952D268B3373007B0BCE /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Tangem DevKit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Tangem DevKit.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - A54215D1256FD8320E9A7CE2 /* Pods_Tangem_DevKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Tangem_DevKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - BD993B71A6F4C4E0CD17F7BD /* Pods-Tangem DevKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tangem DevKit.debug.xcconfig"; path = "Target Support Files/Pods-Tangem DevKit/Pods-Tangem DevKit.debug.xcconfig"; sourceTree = ""; }; - C9A49CB04432C2F706E9F7EB /* Pods-Tangem DevKit.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tangem DevKit.profile.xcconfig"; path = "Target Support Files/Pods-Tangem DevKit/Pods-Tangem DevKit.profile.xcconfig"; sourceTree = ""; }; - CF7C2B205B5D73E75615718A /* Pods-Tangem DevKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tangem DevKit.release.xcconfig"; path = "Target Support Files/Pods-Tangem DevKit/Pods-Tangem DevKit.release.xcconfig"; sourceTree = ""; }; - DDFD9136A4E1F70D8F2BB736 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + C61C09B78255B9DC5704B4C5 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DD859713615359523B05CDDE /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -60,14 +55,21 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5D3F3CFC25CAA8BC00BA3BC8 /* App.framework in Frameworks */, - FE8E94330493F527E1283A8C /* Pods_Tangem_DevKit.framework in Frameworks */, + 4E7BA296778EAA717D7B3614 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 81FDCC4795E568773F142C14 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C61C09B78255B9DC5704B4C5 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -85,15 +87,15 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - BC08B59DEF1C545A67596105 /* Pods */, - F75C03F14861BE7370BEB05F /* Frameworks */, + F7D6F278003A8834356819F3 /* Pods */, + 81FDCC4795E568773F142C14 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( - 97C146EE1CF9000F007C117D /* Tangem DevKit.app */, + 97C146EE1CF9000F007C117D /* Runner.app */, ); name = Products; sourceTree = ""; @@ -101,12 +103,11 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( - 5DE841A3246D272300AE6931 /* Runner.entitlements */, + 5DE3952D268B3373007B0BCE /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, @@ -115,58 +116,40 @@ path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { + F7D6F278003A8834356819F3 /* Pods */ = { isa = PBXGroup; children = ( + DD859713615359523B05CDDE /* Pods-Runner.debug.xcconfig */, + 274A521632525F3F9DB03A17 /* Pods-Runner.release.xcconfig */, + 3FA1D002D52E3F5FDF1BC105 /* Pods-Runner.profile.xcconfig */, ); - name = "Supporting Files"; - sourceTree = ""; - }; - BC08B59DEF1C545A67596105 /* Pods */ = { - isa = PBXGroup; - children = ( - 211F0E5BAD43410CA55ECEE2 /* Pods-Runner.debug.xcconfig */, - DDFD9136A4E1F70D8F2BB736 /* Pods-Runner.release.xcconfig */, - 66D8E5CB1DF2E9A73602813A /* Pods-Runner.profile.xcconfig */, - BD993B71A6F4C4E0CD17F7BD /* Pods-Tangem DevKit.debug.xcconfig */, - CF7C2B205B5D73E75615718A /* Pods-Tangem DevKit.release.xcconfig */, - C9A49CB04432C2F706E9F7EB /* Pods-Tangem DevKit.profile.xcconfig */, - ); + name = Pods; path = Pods; sourceTree = ""; }; - F75C03F14861BE7370BEB05F /* Frameworks */ = { - isa = PBXGroup; - children = ( - 5D3F3CFB25CAA8BC00BA3BC8 /* App.framework */, - A54215D1256FD8320E9A7CE2 /* Pods_Tangem_DevKit.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 97C146ED1CF9000F007C117D /* Tangem DevKit */ = { + 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; - buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Tangem DevKit" */; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - C63BF11100DAB782052ACD7B /* [CP] Check Pods Manifest.lock */, + 1C8047A9A95B48009C92A486 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 21A9DB3CE5A2D07C4AA6AE68 /* [CP] Embed Pods Frameworks */, + 0301E404DD96C2B851A9E10E /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); - name = "Tangem DevKit"; + name = Runner; productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Tangem DevKit.app */; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -176,17 +159,16 @@ isa = PBXProject; attributes = { LastUpgradeCheck = 1020; - ORGANIZATIONNAME = "The Chromium Authors"; + ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = 4897UJ6D8C; LastSwiftMigration = 1100; }; }; }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Tangem DevKit" */; - compatibilityVersion = "Xcode 3.2"; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -198,7 +180,7 @@ projectDirPath = ""; projectRoot = ""; targets = ( - 97C146ED1CF9000F007C117D /* Tangem DevKit */, + 97C146ED1CF9000F007C117D /* Runner */, ); }; /* End PBXProject section */ @@ -218,70 +200,72 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 21A9DB3CE5A2D07C4AA6AE68 /* [CP] Embed Pods Frameworks */ = { + 0301E404DD96C2B851A9E10E /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputPaths = ( + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; - outputPaths = ( + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Tangem DevKit/Pods-Tangem DevKit-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + 1C8047A9A95B48009C92A486 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "Thin Binary"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Run Script"; + name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - C63BF11100DAB782052ACD7B /* [CP] Check Pods Manifest.lock */ = { + 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( ); + name = "Run Script"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Tangem DevKit-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; /* End PBXShellScriptBuildPhase section */ @@ -374,21 +358,15 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 8; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 4897UJ6D8C; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.tangem.devkit; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -498,7 +476,8 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -511,21 +490,15 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 8; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 4897UJ6D8C; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.tangem.devkit; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -543,21 +516,15 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 8; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 4897UJ6D8C; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.tangem.devkit; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -570,7 +537,7 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Tangem DevKit" */ = { + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, @@ -580,7 +547,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Tangem DevKit" */ = { + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, diff --git a/ios/Tangem DevKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 71% rename from ios/Tangem DevKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a1..919434a 100644 --- a/ios/Tangem DevKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/ios/Tangem DevKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from ios/Tangem DevKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/ios/Tangem DevKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 78% rename from ios/Tangem DevKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index 18d9810..f9b0d7c 100644 --- a/ios/Tangem DevKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -2,7 +2,7 @@ - IDEDidComputeMac32BitWarning - + PreviewsEnabled + diff --git a/ios/Tangem DevKit.xcodeproj/xcshareddata/xcschemes/Tangem DevKit.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 78% rename from ios/Tangem DevKit.xcodeproj/xcshareddata/xcschemes/Tangem DevKit.xcscheme rename to ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ae6a7b7..a28140c 100644 --- a/ios/Tangem DevKit.xcodeproj/xcshareddata/xcschemes/Tangem DevKit.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + BuildableName = "Runner.app" + BlueprintName = "Runner" + ReferencedContainer = "container:Runner.xcodeproj"> @@ -27,17 +27,19 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + BuildableName = "Runner.app" + BlueprintName = "Runner" + ReferencedContainer = "container:Runner.xcodeproj"> - - + + + BuildableName = "Runner.app" + BlueprintName = "Runner" + ReferencedContainer = "container:Runner.xcodeproj"> + + + BuildableName = "Runner.app" + BlueprintName = "Runner" + ReferencedContainer = "container:Runner.xcodeproj"> diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 9bb7689..21a3cc1 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -2,7 +2,7 @@ + location = "group:Runner.xcodeproj"> diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h index 7335fdf..308a2a5 100644 --- a/ios/Runner/Runner-Bridging-Header.h +++ b/ios/Runner/Runner-Bridging-Header.h @@ -1 +1 @@ -#import "GeneratedPluginRegistrant.h" \ No newline at end of file +#import "GeneratedPluginRegistrant.h" diff --git a/ios/Tangem DevKit.xcworkspace/contents.xcworkspacedata b/ios/Tangem DevKit.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index b83c8b5..0000000 --- a/ios/Tangem DevKit.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/plugin/tangem-sdk-flutter/ios/Classes/SwiftTangemSdkPlugin.swift b/plugin/tangem-sdk-flutter/ios/Classes/SwiftTangemSdkPlugin.swift index b5868f5..2e8650d 100644 --- a/plugin/tangem-sdk-flutter/ios/Classes/SwiftTangemSdkPlugin.swift +++ b/plugin/tangem-sdk-flutter/ios/Classes/SwiftTangemSdkPlugin.swift @@ -12,53 +12,61 @@ public class SwiftTangemSdkPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "tangemSdk", binaryMessenger: registrar.messenger()) + let channelJS = FlutterMethodChannel(name: "tangemSdk_JSONRPC", binaryMessenger: registrar.messenger()) let instance = SwiftTangemSdkPlugin() registrar.addMethodCallDelegate(instance, channel: channel) + registrar.addMethodCallDelegate(instance, channel: channelJS) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { do { switch call.method { - case "scanCard": - try scanCard(call.arguments, result) - case "sign": - try sign(call.arguments, result) - case "personalize": - try personalize(call.arguments, result) - case "depersonalize": - try depersonalize(call.arguments, result) - case "createWallet": - try createWallet(call.arguments, result) - case "purgeWallet": - try purgeWallet(call.arguments, result) - case "readIssuerData": - try readIssuerData(call.arguments, result) - case "writeIssuerData": - try writeIssuerData(call.arguments, result) - case "readIssuerExData": - try readIssuerExData(call.arguments, result) - case "writeIssuerExData": - try writeIssuerExData(call.arguments, result) - case "readUserData": - try readUserData(call.arguments, result) - case "writeUserData": - try writeUserData(call.arguments, result) - case "writeUserProtectedData": - try writeUserProtectedData(call.arguments, result) - case "setPin1": - try setPin1(call.arguments, result) - case "setPin2": - try setPin2(call.arguments, result) - case "allowsOnlyDebugCards": - allowsOnlyDebugCards(call.arguments, result) - case "prepareHashes": - try prepareHashes(call.arguments, result) + case "runJSONRPCRequest": + try runJSONRPCRequest(call.arguments, result) + case "startSession": + result("null") + case "stopSession": + result("null") +// case "scanCard": +// try scanCard(call.arguments, result) +// case "sign": +// try sign(call.arguments, result) +// case "personalize": +// try personalize(call.arguments, result) +// case "depersonalize": +// try depersonalize(call.arguments, result) +// case "createWallet": +// try createWallet(call.arguments, result) +// case "purgeWallet": +// try purgeWallet(call.arguments, result) +// case "readIssuerData": +// try readIssuerData(call.arguments, result) +// case "writeIssuerData": +// try writeIssuerData(call.arguments, result) +// case "readIssuerExData": +// try readIssuerExData(call.arguments, result) +// case "writeIssuerExData": +// try writeIssuerExData(call.arguments, result) +// case "readUserData": +// try readUserData(call.arguments, result) +// case "writeUserData": +// try writeUserData(call.arguments, result) +// case "writeUserProtectedData": +// try writeUserProtectedData(call.arguments, result) +// case "setPin1": +// try setPin1(call.arguments, result) +// case "setPin2": +// try setPin2(call.arguments, result) +// case "allowsOnlyDebugCards": +// allowsOnlyDebugCards(call.arguments, result) +// case "prepareHashes": +// try prepareHashes(call.arguments, result) // case "writeFiles": // try writeFiles(call.arguments, result) - case "readFiles": - try readFiles(call.arguments, result) - case "deleteFiles": - try deleteFiles(call.arguments, result) +// case "readFiles": +// try readFiles(call.arguments, result) +// case "deleteFiles": +// try deleteFiles(call.arguments, result) // case "changeFilesSettings": // try changeFilesSettings(call.arguments, result) default: @@ -67,180 +75,196 @@ public class SwiftTangemSdkPlugin: NSObject, FlutterPlugin { } catch { print(error) print(error.localizedDescription) - handleError(error, result) + let flutterError = error as? FlutterError ?? .underlyingError(error) + result(flutterError) } } - private func scanCard(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.scanCard(onlineVerification: false, - walletIndex: nil, - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) + private func runJSONRPCRequest(_ args: Any?, _ completion: @escaping FlutterResult) throws { + guard let request: String = getArg(for: .request, from: args) else { + throw FlutterError.missingRequest } - } - - private func sign(_ args: Any?, _ completion: @escaping FlutterResult) throws { - let hexHashes: [String] = try getArg(.hashes, from: args) - let hashes = hexHashes.compactMap({Data(hexString: $0)}) - sdk.sign(hashes: hashes, - cardId: try getArgOptional(.cid, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args), - pin2: try getArgOptional(.pin2, from: args) - ) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func personalize(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.personalize(config: try getArg(.cardConfig, from: args), - issuer: try getArg(.issuer, from: args), - manufacturer: try getArg(.manufacturer, from: args), - acquirer: try getArgOptional(.acquirer, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func depersonalize(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.depersonalize(initialMessage: try getArgOptional(.initialMessage, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func createWallet(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.createWallet(cardId: try getArgOptional(.cid, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args), - pin2: try getArgOptional(.pin2, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func purgeWallet(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.purgeWallet(cardId: try getArgOptional(.cid, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args), - pin2: try getArgOptional(.pin2, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func readIssuerData(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.readIssuerData(cardId: try getArgOptional(.cid, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func writeIssuerData(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.writeIssuerData(cardId: try getArg(.cid, from: args), - issuerData: try getArg(.issuerData, from: args), - issuerDataSignature: try getArg(.issuerDataSignature, from: args), - issuerDataCounter: try getArgOptional(.issuerDataCounter, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func readIssuerExData(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.readIssuerExtraData(cardId: try getArgOptional(.cid, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func writeIssuerExData(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.writeIssuerExtraData(cardId: try getArg(.cid, from: args), - issuerData: try getArg(.issuerData, from: args), - startingSignature: try getArg(.startingSignature, from: args), - finalizingSignature: try getArg(.finalizingSignature, from: args), - issuerDataCounter: try getArgOptional(.issuerDataCounter, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args)){ [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func readUserData(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.readUserData(cardId: try getArgOptional(.cid, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func writeUserData(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.writeUserData(cardId: try getArgOptional(.cid, from: args), - userData: try getArg(.userData, from: args), - userCounter: try getArgOptional(.userCounter, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args)){ [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func writeUserProtectedData(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.writeUserProtectedData(cardId: try getArgOptional(.cid, from: args), - userProtectedData: try getArg(.userProtectedData, from: args), - userProtectedCounter: try getArgOptional(.userProtectedCounter, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func setPin1(_ args: Any?, _ completion: @escaping FlutterResult) throws { - let pin: String? = try getArgOptional(.pinCode, from: args) + let cardId: String? = getArg(for: .cardId, from: args) + let initialMessage: String? = getArg(for: .initialMessage, from: args) - sdk.changePin1(cardId: try getArgOptional(.cid, from: args), - pin: pin?.sha256(), - initialMessage: try getArgOptional(.initialMessage, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) + sdk.startSession(with: request, + cardId: cardId, + initialMessage: initialMessage) { result in + completion(result) } } - private func setPin2(_ args: Any?, _ completion: @escaping FlutterResult) throws { - let pin: String? = try getArgOptional(.pinCode, from: args) - - sdk.changePin2(cardId: try getArgOptional(.cid, from: args), - pin: pin?.sha256(), - initialMessage: try getArgOptional(.initialMessage, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func allowsOnlyDebugCards(_ args: Any?, _ completion: @escaping FlutterResult) { - if let allowedOnlyDebug = (args as? [String: Any])?["isAllowedOnlyDebugCards"] as? Bool { - sdk.config.allowedCardTypes = allowedOnlyDebug ? [.sdk] : FirmwareType.allCases - } - } - - private func prepareHashes(_ args: Any?, _ completion: @escaping FlutterResult) throws { - let hashes = sdk.prepareHashes(cardId: try getArg(.cid, from: args), - fileData: try getArg(.fileData, from: args), - fileCounter: try getArg(.fileCounter, from: args), - privateKey: try getArgOptional(.privateKey, from: args)) - completion(hashes.description) - } - - private func readFiles(_ args: Any?, _ completion: @escaping FlutterResult) throws { - let readPrivateFiles: Bool = try getArgOptional(.readPrivateFiles, from: args) ?? false - //let indices: [Int] = try getArg(.indices, from: args) - - sdk.readFiles(cardId: try getArgOptional(.cid, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args), - pin2: try getArgOptional(.pin2, from: args), - readSettings: ReadFilesTaskSettings(readPrivateFiles: readPrivateFiles)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } +// private func scanCard(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.scanCard(onlineVerification: false, +// walletIndex: nil, +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func sign(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// let hexHashes: [String] = try getArg(.hashes, from: args) +// let hashes = hexHashes.compactMap({Data(hexString: $0)}) +// +// sdk.sign(hashes: hashes, +// cardId: try getArgOptional(.cid, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args), +// pin2: try getArgOptional(.pin2, from: args) +// ) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func personalize(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.personalize(config: try getArg(.cardConfig, from: args), +// issuer: try getArg(.issuer, from: args), +// manufacturer: try getArg(.manufacturer, from: args), +// acquirer: try getArgOptional(.acquirer, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func depersonalize(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.depersonalize(initialMessage: try getArgOptional(.initialMessage, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func createWallet(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.createWallet(cardId: try getArgOptional(.cid, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args), +// pin2: try getArgOptional(.pin2, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func purgeWallet(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.purgeWallet(cardId: try getArgOptional(.cid, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args), +// pin2: try getArgOptional(.pin2, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func readIssuerData(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.readIssuerData(cardId: try getArgOptional(.cid, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func writeIssuerData(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.writeIssuerData(cardId: try getArg(.cid, from: args), +// issuerData: try getArg(.issuerData, from: args), +// issuerDataSignature: try getArg(.issuerDataSignature, from: args), +// issuerDataCounter: try getArgOptional(.issuerDataCounter, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func readIssuerExData(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.readIssuerExtraData(cardId: try getArgOptional(.cid, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func writeIssuerExData(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.writeIssuerExtraData(cardId: try getArg(.cid, from: args), +// issuerData: try getArg(.issuerData, from: args), +// startingSignature: try getArg(.startingSignature, from: args), +// finalizingSignature: try getArg(.finalizingSignature, from: args), +// issuerDataCounter: try getArgOptional(.issuerDataCounter, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args)){ [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func readUserData(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.readUserData(cardId: try getArgOptional(.cid, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func writeUserData(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.writeUserData(cardId: try getArgOptional(.cid, from: args), +// userData: try getArg(.userData, from: args), +// userCounter: try getArgOptional(.userCounter, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args)){ [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func writeUserProtectedData(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.writeUserProtectedData(cardId: try getArgOptional(.cid, from: args), +// userProtectedData: try getArg(.userProtectedData, from: args), +// userProtectedCounter: try getArgOptional(.userProtectedCounter, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func setPin1(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// let pin: String? = try getArgOptional(.pinCode, from: args) +// +// sdk.changePin1(cardId: try getArgOptional(.cid, from: args), +// pin: pin?.sha256(), +// initialMessage: try getArgOptional(.initialMessage, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func setPin2(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// let pin: String? = try getArgOptional(.pinCode, from: args) +// +// sdk.changePin2(cardId: try getArgOptional(.cid, from: args), +// pin: pin?.sha256(), +// initialMessage: try getArgOptional(.initialMessage, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func allowsOnlyDebugCards(_ args: Any?, _ completion: @escaping FlutterResult) { +// if let allowedOnlyDebug = (args as? [String: Any])?["isAllowedOnlyDebugCards"] as? Bool { +// sdk.config.allowedCardTypes = allowedOnlyDebug ? [.sdk] : FirmwareType.allCases +// } +// } +//// +// private func prepareHashes(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// let hashes = sdk.prepareHashes(cardId: try getArg(.cid, from: args), +// fileData: try getArg(.fileData, from: args), +// fileCounter: try getArg(.fileCounter, from: args), +// privateKey: try getArgOptional(.privateKey, from: args)) +// completion(hashes.description) +// } +// +// private func readFiles(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// let readPrivateFiles: Bool = try getArgOptional(.readPrivateFiles, from: args) ?? false +// //let indices: [Int] = try getArg(.indices, from: args) +// +// sdk.readFiles(cardId: try getArgOptional(.cid, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args), +// pin2: try getArgOptional(.pin2, from: args), +// readSettings: ReadFilesTaskSettings(readPrivateFiles: readPrivateFiles)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } // private func writeFiles(_ args: Any?, _ completion: @escaping FlutterResult) throws { // sdk.writeFiles(cardId: try getArgOptional(.cid, from: args), @@ -263,150 +287,149 @@ public class SwiftTangemSdkPlugin: NSObject, FlutterPlugin { // } // } - private func deleteFiles(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.deleteFiles(cardId: try getArgOptional(.cid, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args), - pin2: try getArgOptional(.pin2, from: args), - indicesToDelete: try getArgOptional(.indices, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } +// private func deleteFiles(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.deleteFiles(cardId: try getArgOptional(.cid, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args), +// pin2: try getArgOptional(.pin2, from: args), +// indicesToDelete: try getArgOptional(.indices, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } - private func handleCompletion(_ sdkResult: Result, _ completion: @escaping FlutterResult) { - switch sdkResult { - case .success(let response): - completion(response.description) - case .failure(let error): - handleError(error, completion) - } - } +// private func handleCompletion(_ sdkResult: Result, _ completion: @escaping FlutterResult) { +// switch sdkResult { +// case .success(let response): +// completion(response.description) +// case .failure(let error): +// handleError(error, completion) +// } +// } +// +// private func handleError(_ error: Error, _ completion: @escaping FlutterResult) { +// var pluginError: PluginError +// +// if let sdkError = error as? TangemSdkError { +// pluginError = sdkError.toPluginError() +// } else if let pluginInternalError = error as? PluginInternalError { +// pluginError = pluginInternalError.toPluginError() +// } else { +// pluginError = PluginError(code: 9998, localizedDescription: "\(error)") +// } +// +// completion(pluginError.flutterError) +// } - private func handleError(_ error: Error, _ completion: @escaping FlutterResult) { - var pluginError: PluginError - - if let sdkError = error as? TangemSdkError { - pluginError = sdkError.toPluginError() - } else if let pluginInternalError = error as? PluginInternalError { - pluginError = pluginInternalError.toPluginError() - } else { - pluginError = PluginError(code: 9998, localizedDescription: "\(error)") +// private func getArgOptional(_ key: ArgKey, from arguments: Any?) throws -> T? { +// do { +// return try getArg(key, from: arguments) +// } catch PluginInternalError.missingArgument { +// return nil +// } +// } +// + private func getArg(for key: ArgKey, from arguments: Any?) -> T? { + if let value = (arguments as? NSDictionary)?[key.rawValue] { + return value as? T } - completion(pluginError.flutterError) - } - - private func getArgOptional(_ key: ArgKey, from arguments: Any?) throws -> T? { - do { - return try getArg(key, from: arguments) - } catch PluginInternalError.missingArgument { - return nil - } - } - - private func getArg(_ key: ArgKey, from arguments: Any?) throws -> T { - if let value = (arguments as? [String: Any])?[key.rawValue] { - if String(describing: value) == "" { - throw PluginInternalError.missingArgument(key) - } - - if T.self == Data.self || T.self == Data?.self { - if let hex = value as? String { - return (Data(hexString: hex) as! T) - } else { - throw PluginInternalError.failedToParseDataFromHex(key) - } - } else { - do { - return try decodeObject(value) - } catch { - if let converted = value as? T { - return converted - } else { - throw error - } - } - } - } else { - throw PluginInternalError.missingArgument(key) - } - } - - private func decodeObject(_ value: Any) throws -> T { - if let json = value as? String, let jsonData = json.data(using: .utf8) { - return try JSONDecoder.tangemSdkDecoder.decode(T.self, from: jsonData) - } else { - throw PluginInternalError.failedToParseJson - } + return nil } +// +// private func decodeObject(_ value: Any) throws -> T { +// if let json = value as? String, let jsonData = json.data(using: .utf8) { +// return try JSONDecoder.tangemSdkDecoder.decode(T.self, from: jsonData) +// } else { +// throw PluginInternalError.failedToParseJson +// } +// } } -fileprivate struct PluginError: Encodable { - let code: Int - let localizedDescription: String - - var jsonDescription: String { - let encoder = JSONEncoder() - encoder.outputFormatting = [.sortedKeys, .prettyPrinted] - let data = (try? encoder.encode(self)) ?? Data() - return String(data: data, encoding: .utf8)! - } - - var flutterError: FlutterError { - FlutterError(code: "\(code)", message: localizedDescription, details: jsonDescription) - } -} +//fileprivate struct PluginError: Encodable { +// let code: Int +// let localizedDescription: String +// +// var jsonDescription: String { +// let encoder = JSONEncoder() +// encoder.outputFormatting = [.sortedKeys, .prettyPrinted] +// let data = (try? encoder.encode(self)) ?? Data() +// return String(data: data, encoding: .utf8)! +// } +// +// var flutterError: FlutterError { +// FlutterError(code: "\(code)", message: localizedDescription, details: jsonDescription) +// } +//} +// +//fileprivate extension TangemSdkError { +// func toPluginError() -> PluginError { +// return PluginError(code: code, localizedDescription: localizedDescription) +// } +//} -fileprivate extension TangemSdkError { - func toPluginError() -> PluginError { - return PluginError(code: code, localizedDescription: localizedDescription) - } +//fileprivate enum PluginInternalError: Error, LocalizedError { +// case failedToParseJson +// case missingArgument(_ value: ArgKey) +// case failedToParseDataFromHex(_ value: ArgKey) +// +// var errorDescription: String? { +// switch self { +// case .failedToParseJson: +// return "Failed to parse JSON object" +// case .missingArgument(let value): +// return "Missing required argument: \(value.rawValue)" +// case .failedToParseDataFromHex(let value): +// return "Failed to parse Data from HEX for: \(value.rawValue)" +// } +// } +// +// func toPluginError() -> PluginError { +// return PluginError(code: 9999 , localizedDescription: localizedDescription) +// } +//} + +fileprivate enum ArgKey: String { +// case pin1 +// case pin2 + case cardId +// case hashes +// case userCounter +// case userProtectedCounter +// case userData +// case issuerDataCounter +// case userProtectedData +// case issuerData +// case issuerDataSignature +// case startingSignature +// case finalizingSignature +// case issuer +// case manufacturer +// case acquirer +// case cardConfig +// case pinCode + case initialMessage +// case fileData +// case fileCounter +// case privateKey +// case readPrivateFiles +// case indices + case request = "JSONRPCRequest" } -fileprivate enum PluginInternalError: Error, LocalizedError { - case failedToParseJson - case missingArgument(_ value: ArgKey) - case failedToParseDataFromHex(_ value: ArgKey) +extension FlutterError: Error {} + +fileprivate extension FlutterError { + static let genericCode = "9999" - var errorDescription: String? { - switch self { - case .failedToParseJson: - return "Failed to parse JSON object" - case .missingArgument(let value): - return "Missing required argument: \(value.rawValue)" - case .failedToParseDataFromHex(let value): - return "Failed to parse Data from HEX for: \(value.rawValue)" - } + static var parseArgumentsError: FlutterError { + FlutterError(code: genericCode, message: "Faield to parse arguments", details: nil) } - func toPluginError() -> PluginError { - return PluginError(code: 9999 , localizedDescription: localizedDescription) + static var missingRequest: FlutterError { + FlutterError(code: genericCode, message: "Missing JSON RPC request", details: nil) + } + + static func underlyingError(_ error: Error) -> FlutterError { + FlutterError(code: genericCode, message: "Some error occured", details: error) } -} - -fileprivate enum ArgKey: String { - case pin1 - case pin2 - case cid - case hashes - case userCounter - case userProtectedCounter - case userData - case issuerDataCounter - case userProtectedData - case issuerData - case issuerDataSignature - case startingSignature - case finalizingSignature - case issuer - case manufacturer - case acquirer - case cardConfig - case pinCode - case initialMessage - case fileData - case fileCounter - case privateKey - case readPrivateFiles - case indices } diff --git a/plugin/tangem-sdk-flutter/ios/tangem_sdk.podspec b/plugin/tangem-sdk-flutter/ios/tangem_sdk.podspec index d55c88f..1350091 100644 --- a/plugin/tangem-sdk-flutter/ios/tangem_sdk.podspec +++ b/plugin/tangem-sdk-flutter/ios/tangem_sdk.podspec @@ -18,7 +18,7 @@ TangemSdk plugin for integration into flutter projects s.ios.deployment_target = '13.0' # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' - s.dependency 'TangemSdk', "~> 2.4.2" + s.dependency 'TangemSdk', "~> 3.0.2" end From efe49595eccff0469a9dab5bbfd075bd68f84225 Mon Sep 17 00:00:00 2001 From: Anton Zhilenkov Date: Sun, 8 Aug 2021 23:17:59 +0300 Subject: [PATCH 12/12] AND-1170 run valid test --- .../{test_json_2.json => test_json_3.json} | 199 +++++++++++------- ...test_json_1.json => test_json_fail_3.json} | 157 +++++++------- lib/app/domain/actions_bloc/abstracts.dart | 16 +- .../domain/actions_bloc/actions_blocs.dart | 4 +- .../domain/model/json_test_model.dart | 12 +- .../domain/model/json_test_model.g.dart | 2 +- lib/app_test_launcher/domain/error/error.dart | 2 + .../domain/error/test_assert_error.dart | 8 + .../domain/executable/assert/assert.dart | 52 ++++- .../domain/executable/step/step_launcher.dart | 11 +- ...alueFinder.dart => json_value_finder.dart} | 0 .../domain/test_launcher.dart | 81 ++++--- .../domain/variable_service.dart | 38 ++-- .../ui/screen/json_test_launcher_screen.dart | 7 +- lib/main.dart | 2 + test/json_value_finder_test.dart | 2 +- test/variable_service_test.dart | 16 +- 17 files changed, 367 insertions(+), 242 deletions(-) rename assets/json/{test_json_2.json => test_json_3.json} (54%) rename assets/json/{test_json_1.json => test_json_fail_3.json} (55%) rename lib/app_test_launcher/domain/{JsonValueFinder.dart => json_value_finder.dart} (100%) diff --git a/assets/json/test_json_2.json b/assets/json/test_json_3.json similarity index 54% rename from assets/json/test_json_2.json rename to assets/json/test_json_3.json index e5b44fd..3f67c8d 100644 --- a/assets/json/test_json_2.json +++ b/assets/json/test_json_3.json @@ -1,7 +1,7 @@ { "setup": { - "name": "Twins 2", - "description": "Two test iterations with two steps iterations", + "name": "Experiment", + "description": "It shows what we can do with a card", "personalizationConfig": { "config": { "issuerName": "TANGEM SDK", @@ -44,7 +44,7 @@ "requireTerminalTxSignature": false, "requireTerminalCertSignature": false, "checkPIN3OnCard": false, - "createWallet": true, + "createWallet": false, "walletsCount": 1, "cardData": { "batchId": "FFFF", @@ -57,10 +57,6 @@ ] }, "ndefRecords": [ - { - "type": "AAR", - "value": "com.tangem.wallet" - }, { "type": "URI", "value": "https://tangem.com" @@ -69,7 +65,7 @@ }, "issuer": { "name": "TANGEM SDK", - "id": "TANGEM SDK\u0000", + "id": "TANGEM SDK", "dataKeyPair": { "publicKey": "045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26", "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92" @@ -81,14 +77,14 @@ }, "acquirer": { "name": "Smart Cash", - "id": "Smart Cash\u0000", + "id": "Smart Cash", "keyPair": { "publicKey": "0456ad1a82b22bcb40c38fd08939f87e6b80e40dec5b3bdb351c55fcd709e47f9fb2ed00c2304d3a986f79c5ae0ac3c84e88da46dc8f513b7542c716af8c9a2daf", "privateKey": "21222324252627284771ED81F2BACF57479E4735EB1405083927372D40DA9E92" } }, "manufacturer": { - "name": "Tangem", + "name": "TANGEM", "keyPair": { "publicKey": "04bab86d56298c996f564a84fc88e28aed38184b12f07e519113bef48c76f3df3adc303599b08ac05b55ec3df98d9338573a6242f76f5d28f4f0f364e87e8fca2f", "privateKey": "1b48cfd24bbb5b394771ed81f2bacf57479e4735eb1405083927372d40da9e92" @@ -99,90 +95,143 @@ "minimalFirmware": null, "platform": null, "iterations": 2, - "creationDateMs": 1624407062610 + "creationDateMs": 1626326543961 }, "steps": [ { - "name": "Scan card", + "name": "0_scan", "method": "SCAN_TASK", "parameters": {}, "expectedResult": { "cardId": "BB03000000000004", "manufacturerName": "TANGEM", - "status": "Loaded", - "firmwareVersion": { - "version": "3.37d SDK" + "status": "Empty" + }, + "asserts": [ + { + "type": "EQUALS", + "fields": [ + "{#parent.actualResult.status}", + "Empty" + ] }, - "cardPublicKey": "0497C0424AF7BF8CE9920CB90EDAC2010FDED904937BC6D1D6E82E9254E0EFE4D4FDF355BB16BD0E9550D8BE5AB741B897FBC70360E99A86B97BA7DCC2FBF77D7E", - "defaultCurve": "Secp256k1", - "settingsMask": [ - "IsReusable", - "AllowSetPIN1", - "AllowSetPIN2", - "UseNDEF", - "UseDynamicNDEF", - "SmartSecurityDelay", - "AllowUnencrypted", - "AllowFastEncryption", - "AllowSelectBlockchain", - "SkipSecurityDelayIfValidatedByLinkedTerminal", - "SkipCheckPIN2CVCIfValidatedByIssuer", - "SkipSecurityDelayIfValidatedByIssuer" - ], - "issuerPublicKey": "045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26", - "signingMethods": [ - "SignHash" + { + "type": "EQUALS", + "fields": [ + "{#setup.personalizationConfig.manufacturer.name}", + "{#parent.actualResult.manufacturerName}" + ] + } + ], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 1 + }, + { + "name": "1_createWallet", + "method": "CREATE_WALLET_TASK", + "parameters": { + "config": { + "isReusable": true, + "prohibitPurgeWallet": null, + "curveId": "Secp256k1", + "signingMethods": "SignHash" + } + }, + "expectedResult": { + "cardId": "BB03000000000004", + "status": "Loaded", + "walletPublicKey": "049AD86A7F7F7696369A39D53CFA619FD1D917608AEF6CAF9F85BC9E7577D28B1C76EFF56384DDB867EFD1A38DE0479240258649A03CB76DF7EB8F8CAE9A8775C0" + }, + "asserts": [ + { + "type": "EQUALS", + "fields": [ + "{#parent.actualResult.status}", + "Loaded" + ] + } + ], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 1 + }, + { + "name": "2_sign", + "method": "SIGN_COMMAND", + "parameters": { + "hashes": [ + "44617461207573656420666f722068617368696e67" ], - "pauseBeforePin2": 500, - "walletsCount": null, - "walletIndex": null, - "health": 0, - "isActivated": false, - "activationSeed": null, - "paymentFlowVersion": null, - "userCounter": null, - "userProtectedCounter": null, - "terminalIsLinked": false, - "cardData": { - "batchId": "FFFF", - "blockchainName": "ETH", - "issuerName": "TANGEM SDK", - "manufacturerSignature": "7CBEABA11F9D564A244260AC15CE0A26AB885811322C38B9FCA9E55EC7654C2027219ABB1BEAD190C2C370F48635129F76E8423FA8003DB6A08D2F64B1574004", - "manufactureDateTime": "2021-06-23", - "productMask": [ - "Note" + "walletPublicKey": "{#1_createWallet.actualResult.walletPublicKey}" + }, + "expectedResult": { + "signedHashes": [ + "2EF25F7E70D4332E4C915A50BA56C3143950DD51130B4723F998AC05A2F5679F03462CCA69E2A3B37372A108CD2BB411FBD00575C9FC30DF4EFB82BB4BF93276" + ] + }, + "asserts": [ + { + "type": "IS_NOT_EMPTY", + "fields": [ + "{#parent.actualResult.signatures}" ] - }, - "isPin1Default": true, - "isPin2Default": true, + } + ], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 2 + }, + { + "name": "scan_after_sign", + "method": "SCAN_TASK", + "parameters": {}, + "expectedResult": { + "cardId": "BB03000000000004", + "manufacturerName": "TANGEM", + "status": "Empty", "wallets": [ { "index": 0, - "status": "Loaded", + "status": "Empty", "curve": "Secp256k1", - "settingsMask": [ - "IsReusable", - "AllowSetPIN1", - "AllowSetPIN2", - "UseNDEF", - "UseDynamicNDEF", - "SmartSecurityDelay", - "AllowUnencrypted", - "AllowFastEncryption", - "AllowSelectBlockchain", - "SkipSecurityDelayIfValidatedByLinkedTerminal", - "SkipCheckPIN2CVCIfValidatedByIssuer", - "SkipSecurityDelayIfValidatedByIssuer" - ], - "publicKey": "0408C8EF8DEC3B9C910B44D8A5C72138A333365ED2DC34E91FACA8B9A1B37EB5010CB0080E8144807998F50F6BE4B1CA4BF140ADFCEF34124D1417DD116535FB2D", - "signedHashes": 0, - "remainingSignatures": 999999 + "signedHashes": 1 } ] }, - "asserts": [], + "asserts": [ + { + "type": "EQUALS", + "fields": [ + "{#parent.actualResult.wallets.0.signedHashes}", + 0 + ] + } + ], "actionType": "NFC_SESSION_RUNNABLE", - "iterations": 2 + "iterations": 1 + }, + { + "name": "4_purgeWallet", + "method": "PURGE_WALLET_COMMAND", + "parameters": { + "walletIndex": 0 + }, + "expectedResult": { + "cardId": "BB03000000000004", + "status": "Empty" + }, + "asserts": [ + { + "type": "SUCCESS" + }, + { + "type": "EQUALS", + "fields": [ + "{#parent.expectedResult.status}", + "Empty" + ] + } + ], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 1 } ] } \ No newline at end of file diff --git a/assets/json/test_json_1.json b/assets/json/test_json_fail_3.json similarity index 55% rename from assets/json/test_json_1.json rename to assets/json/test_json_fail_3.json index 3843ab0..acba8a8 100644 --- a/assets/json/test_json_1.json +++ b/assets/json/test_json_fail_3.json @@ -1,7 +1,7 @@ { "setup": { - "name": "Twins", - "description": "Two test iterations with two steps iterations", + "name": "Experiment with fails", + "description": "It shows what we can do with a card", "personalizationConfig": { "config": { "issuerName": "TANGEM SDK", @@ -44,7 +44,7 @@ "requireTerminalTxSignature": false, "requireTerminalCertSignature": false, "checkPIN3OnCard": false, - "createWallet": true, + "createWallet": false, "walletsCount": 1, "cardData": { "batchId": "FFFF", @@ -57,10 +57,6 @@ ] }, "ndefRecords": [ - { - "type": "AAR", - "value": "com.tangem.wallet" - }, { "type": "URI", "value": "https://tangem.com" @@ -69,7 +65,7 @@ }, "issuer": { "name": "TANGEM SDK", - "id": "TANGEM SDK\u0000", + "id": "TANGEM SDK", "dataKeyPair": { "publicKey": "045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26", "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92" @@ -81,14 +77,14 @@ }, "acquirer": { "name": "Smart Cash", - "id": "Smart Cash\u0000", + "id": "Smart Cash", "keyPair": { "publicKey": "0456ad1a82b22bcb40c38fd08939f87e6b80e40dec5b3bdb351c55fcd709e47f9fb2ed00c2304d3a986f79c5ae0ac3c84e88da46dc8f513b7542c716af8c9a2daf", "privateKey": "21222324252627284771ED81F2BACF57479E4735EB1405083927372D40DA9E92" } }, "manufacturer": { - "name": "Tangem", + "name": "TANGEM", "keyPair": { "publicKey": "04bab86d56298c996f564a84fc88e28aed38184b12f07e519113bef48c76f3df3adc303599b08ac05b55ec3df98d9338573a6242f76f5d28f4f0f364e87e8fca2f", "privateKey": "1b48cfd24bbb5b394771ed81f2bacf57479e4735eb1405083927372d40da9e92" @@ -98,89 +94,88 @@ "sdkConfig": {}, "minimalFirmware": null, "platform": null, - "iterations": 2, - "creationDateMs": 1624407062610 + "iterations": 1, + "creationDateMs": 1626326543961 }, "steps": [ { - "name": "Scan card", + "name": "0_scan", "method": "SCAN_TASK", "parameters": {}, "expectedResult": { "cardId": "BB03000000000004", "manufacturerName": "TANGEM", - "status": "Loaded", - "firmwareVersion": { - "version": "3.37d SDK" - }, - "cardPublicKey": "0497C0424AF7BF8CE9920CB90EDAC2010FDED904937BC6D1D6E82E9254E0EFE4D4FDF355BB16BD0E9550D8BE5AB741B897FBC70360E99A86B97BA7DCC2FBF77D7E", - "defaultCurve": "Secp256k1", - "settingsMask": [ - "IsReusable", - "AllowSetPIN1", - "AllowSetPIN2", - "UseNDEF", - "UseDynamicNDEF", - "SmartSecurityDelay", - "AllowUnencrypted", - "AllowFastEncryption", - "AllowSelectBlockchain", - "SkipSecurityDelayIfValidatedByLinkedTerminal", - "SkipCheckPIN2CVCIfValidatedByIssuer", - "SkipSecurityDelayIfValidatedByIssuer" - ], - "issuerPublicKey": "045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26", - "signingMethods": [ - "SignHash" - ], - "pauseBeforePin2": 500, - "walletsCount": null, - "walletIndex": null, - "health": 0, - "isActivated": false, - "activationSeed": null, - "paymentFlowVersion": null, - "userCounter": null, - "userProtectedCounter": null, - "terminalIsLinked": false, - "cardData": { - "batchId": "FFFF", - "blockchainName": "ETH", - "issuerName": "TANGEM SDK", - "manufacturerSignature": "7CBEABA11F9D564A244260AC15CE0A26AB885811322C38B9FCA9E55EC7654C2027219ABB1BEAD190C2C370F48635129F76E8423FA8003DB6A08D2F64B1574004", - "manufactureDateTime": "2021-06-23", - "productMask": [ - "Note" + "status": "Empty" + }, + "asserts": [ + { + "type": "EQUALS", + "fields": [ + "{#parent.actualResult.status}", + "Empty" ] }, - "isPin1Default": true, - "isPin2Default": true, - "wallets": [ - { - "index": 0, - "status": "Loaded", - "curve": "Secp256k1", - "settingsMask": [ - "IsReusable", - "AllowSetPIN1", - "AllowSetPIN2", - "UseNDEF", - "UseDynamicNDEF", - "SmartSecurityDelay", - "AllowUnencrypted", - "AllowFastEncryption", - "AllowSelectBlockchain", - "SkipSecurityDelayIfValidatedByLinkedTerminal", - "SkipCheckPIN2CVCIfValidatedByIssuer", - "SkipSecurityDelayIfValidatedByIssuer" - ], - "publicKey": "0408C8EF8DEC3B9C910B44D8A5C72138A333365ED2DC34E91FACA8B9A1B37EB5010CB0080E8144807998F50F6BE4B1CA4BF140ADFCEF34124D1417DD116535FB2D", - "signedHashes": 0, - "remainingSignatures": 999999 - } + { + "type": "EQUALS", + "fields": [ + "{#setup.personalizationConfig.manufacturer.name}", + "{#parent.actualResult.manufacturerName}" + ] + } + ], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 1 + }, + { + "name": "1_createWallet", + "method": "CREATE_WALLET_TASK", + "parameters": { + "config": { + "isReusable": true, + "prohibitPurgeWallet": null, + "curveId": "Secp256k1", + "signingMethods": "SignHash" + } + }, + "expectedResult": { + "cardId": "BB03000000000004", + "status": "Loaded", + "walletPublicKey": "049AD86A7F7F7696369A39D53CFA619FD1D917608AEF6CAF9F85BC9E7577D28B1C76EFF56384DDB867EFD1A38DE0479240258649A03CB76DF7EB8F8CAE9A8775C0" + }, + "asserts": [ + { + "type": "EQUALS", + "fields": [ + "{#parent.actualResult.status}", + "Loaded" + ] + } + ], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 1 + }, + { + "name": "2_sign", + "method": "SIGN_COMMAND", + "parameters": { + "hashes": [ + "44617461207573656420666f722068617368696e67" + ], + "walletPublicKey": "{#1_createWallet.actualResult.walletPublicKey}" + }, + "expectedResult": { + "signedHashes": [ + "2EF25F7E70D4332E4C915A50BA56C3143950DD51130B4723F998AC05A2F5679F03462CCA69E2A3B37372A108CD2BB411FBD00575C9FC30DF4EFB82BB4BF93276" ] }, - "asserts": [], + "asserts": [ + { + "type": "IS_NOT_EMPTY", + "fields": [ + "{#parent.actualResult.walletPublicKey}" + ] + } + ], "actionType": "NFC_SESSION_RUNNABLE", "iterations": 2 } diff --git a/lib/app/domain/actions_bloc/abstracts.dart b/lib/app/domain/actions_bloc/abstracts.dart index b690931..9041b0e 100644 --- a/lib/app/domain/actions_bloc/abstracts.dart +++ b/lib/app/domain/actions_bloc/abstracts.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:devkit/app/domain/model/command_data_models.dart'; import 'package:devkit/commons/common_abstracts.dart'; +import 'package:devkit/main.dart'; import 'package:rxdart/rxdart.dart'; import 'package:tangem_sdk/tangem_sdk.dart'; @@ -47,7 +48,20 @@ abstract class ActionBloc extends BaseBloc { } invokeAction() async { - createCommandData((commandData) => _prepareCommandAndRun(commandData, callback), sendSnackbarMessage); + createCommandData((commandData) { + if (commandData.type == TangemSdk.cCreateWallet) { + _prepareCommandAndRun( + commandData, + Callback((result) { + if (result is CreateWalletResponse) { + gWalletPublicKey = result.walletPublicKey; + } + callback.onSuccess(result); + }, callback.onError)); + } else { + _prepareCommandAndRun(commandData, callback); + } + }, sendSnackbarMessage); } _prepareCommandAndRun(CommandDataModel commandData, Callback callback) async { diff --git a/lib/app/domain/actions_bloc/actions_blocs.dart b/lib/app/domain/actions_bloc/actions_blocs.dart index 1d1fe97..530ba5a 100644 --- a/lib/app/domain/actions_bloc/actions_blocs.dart +++ b/lib/app/domain/actions_bloc/actions_blocs.dart @@ -5,6 +5,7 @@ import 'package:devkit/app/domain/actions_bloc/personalize/personalization_value import 'package:devkit/app/domain/model/command_data_models.dart'; import 'package:devkit/app/domain/model/personalization/utils.dart'; import 'package:devkit/commons/common_abstracts.dart'; +import 'package:devkit/main.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart'; @@ -368,8 +369,7 @@ class SignBloc extends ActionBloc { addSubscription(bsWalletPublicKey.stream.listen((event) => _walletPublicKey = event)); bsDataForHashing.add("Data used for hashing"); //TODO: before trying to sign, must read the card and fetch walletPubKey (v.<4) - by 0 index, (v.>=4) - by using slider - bsWalletPublicKey.add( - "04B2A74E1E502A3E5C4B03B53412A5891F270752543D77B5FE685F3125196610E43C880E29ADA29B2D9641FEAB37A699355863F920DE98937B426B1F303A4752C5"); + bsWalletPublicKey.add(gWalletPublicKey); } @override diff --git a/lib/app_test_assembler/domain/model/json_test_model.dart b/lib/app_test_assembler/domain/model/json_test_model.dart index c6eec32..e5381b2 100644 --- a/lib/app_test_assembler/domain/model/json_test_model.dart +++ b/lib/app_test_assembler/domain/model/json_test_model.dart @@ -86,7 +86,7 @@ class TestSetup { class StepModel { final String name; final String method; - final Map params; + final Map params = {}; final Map expectedResult; final List asserts; final String actionType; @@ -99,14 +99,12 @@ class StepModel { StepModel( this.name, this.method, - this.params, + Map params, this.expectedResult, this.asserts, this.actionType, this.iterations, - ) { - this._rawParams.addAll(params); - } + ) : this._rawParams = params; factory StepModel.getDefault() { return StepModel("stepName", "methodName", {}, {}, [], "NFC_SESSION_RUNNABLE", 1); @@ -132,7 +130,7 @@ class StepModel { return StepModel( name ?? this.name, method ?? this.method, - params ?? this.params, + params ?? this._rawParams, expectedResult ?? this.expectedResult, asserts ?? this.asserts, actionType ?? this.actionType, @@ -144,7 +142,7 @@ class StepModel { @JsonSerializable() class AssertModel { final String type; - final List fields; + final List? fields; AssertModel(this.type, this.fields); diff --git a/lib/app_test_assembler/domain/model/json_test_model.g.dart b/lib/app_test_assembler/domain/model/json_test_model.g.dart index f8f797f..7d1e08b 100644 --- a/lib/app_test_assembler/domain/model/json_test_model.g.dart +++ b/lib/app_test_assembler/domain/model/json_test_model.g.dart @@ -67,7 +67,7 @@ Map _$TestStepToJson(StepModel instance) => { AssertModel _$TestAssertFromJson(Map json) { return AssertModel( json['type'] as String, - (json['fields'] as List).map((e) => e as String).toList(), + (json['fields'] == null ? null : (json['fields'] as List).map((e) => e).toList()), ); } diff --git a/lib/app_test_launcher/domain/error/error.dart b/lib/app_test_launcher/domain/error/error.dart index 3f4b123..db67248 100644 --- a/lib/app_test_launcher/domain/error/error.dart +++ b/lib/app_test_launcher/domain/error/error.dart @@ -17,6 +17,8 @@ class TangemSdkPluginWrappedError extends TestFrameworkError { extension OnJSONRPCError on JSONRPCError { bool isInterruptTest() { switch (code) { + case -32000: + return data != null && data!.contains("code: 50002"); case 1000: return true; case 50002: diff --git a/lib/app_test_launcher/domain/error/test_assert_error.dart b/lib/app_test_launcher/domain/error/test_assert_error.dart index 05bdc6e..5e467db 100644 --- a/lib/app_test_launcher/domain/error/test_assert_error.dart +++ b/lib/app_test_launcher/domain/error/test_assert_error.dart @@ -20,3 +20,11 @@ class EqualsError extends TestAssertError { @override String get errorMessage => "Fields doesn't match. f1: $firstValue, f2: $secondValue"; } + +class IsNotEmptyError extends TestAssertError { + final String fieldName; + + IsNotEmptyError(this.fieldName); + @override + String get errorMessage => "Field $fieldName is empty"; +} diff --git a/lib/app_test_launcher/domain/executable/assert/assert.dart b/lib/app_test_launcher/domain/executable/assert/assert.dart index 72083f3..758522b 100644 --- a/lib/app_test_launcher/domain/executable/assert/assert.dart +++ b/lib/app_test_launcher/domain/executable/assert/assert.dart @@ -7,8 +7,14 @@ import 'package:devkit/app_test_launcher/domain/executable/executable.dart'; import 'package:devkit/app_test_launcher/domain/variable_service.dart'; class AssertsFactory { + Map _assertsBuilders = {}; + + registerAssert(String type, TestAssert Function() assertBuilder) { + _assertsBuilders[type] = assertBuilder; + } + TestAssert? getAssert(String type) { - return null; + return _assertsBuilders[type]?.call(); } } @@ -16,30 +22,34 @@ abstract class TestAssert implements Executable { final String _type; late String parentName; - late List fields; + late List fields; TestAssert(this._type); - void init(String parentName, List fields) { + void init(String parentName, List? fields) { this.parentName = parentName; - this.fields = fields; + this.fields = fields == null ? [] : fields; } @override void run(OnComplete callback); - dynamic getFieldValue(String pointer) { - return VariableService.getStepValue(parentName, pointer); + dynamic _getFieldValue(dynamic pointer) { + return pointer is String ? VariableService.getValue(parentName, pointer) : pointer; } + + static String equals = "EQUALS"; + static String isNotEmpty = "IS_NOT_EMPTY"; + static String success = "SUCCESS"; } class EqualsAssert extends TestAssert { - EqualsAssert() : super("EQUALS"); + EqualsAssert() : super(TestAssert.equals); @override run(OnComplete callback) { - final firstValue = getFieldValue(fields[0]); - final secondValue = getFieldValue(fields[1]); + final firstValue = _getFieldValue(fields[0]); + final secondValue = _getFieldValue(fields[1]); if (firstValue == secondValue) { callback(AssertSuccess(_type)); } else { @@ -47,3 +57,27 @@ class EqualsAssert extends TestAssert { } } } + +class IsNotEmptyAssert extends TestAssert { + IsNotEmptyAssert() : super(TestAssert.isNotEmpty); + + @override + void run(OnComplete callback) { + final value = _getFieldValue(fields[0]); + if (value != null) { + callback(AssertSuccess(_type)); + } else { + _getFieldValue(fields[0]); + callback(AssertFailure(_type, IsNotEmptyError("${fields[0]}"))); + } + } +} + +class SuccessAssert extends TestAssert { + SuccessAssert() : super(TestAssert.success); + + @override + void run(OnComplete callback) { + callback(AssertSuccess(_type)); + } +} diff --git a/lib/app_test_launcher/domain/executable/step/step_launcher.dart b/lib/app_test_launcher/domain/executable/step/step_launcher.dart index 7f164a8..0b45b30 100644 --- a/lib/app_test_launcher/domain/executable/step/step_launcher.dart +++ b/lib/app_test_launcher/domain/executable/step/step_launcher.dart @@ -53,10 +53,9 @@ class StepLauncher implements Executable { void _fetchVariables() { _model.params.clear(); _model.rawParams.forEach((key, value) { - _model.params[key] = VariableService.getStepValue(_model.name, value); - }); - _model.expectedResult.forEach((key, value) { - _model.expectedResult[key] = VariableService.getStepValue(_model.name, value); + //TODO: добавить раскрытие переменных во вложенных структурах + final extractedValue = VariableService.getValue(_model.name, value); + _model.params[key] = extractedValue; }); } @@ -67,7 +66,7 @@ class StepLauncher implements Executable { } final assertsQueue = Queue(); - _model.asserts.forEach((element) { + for (final element in _model.asserts){ final testAssert = _assertsFactory.getAssert(element.type); if (testAssert == null) { callback(StepFailure(_model.name, AssertNotRegisteredError(element.type))); @@ -76,7 +75,7 @@ class StepLauncher implements Executable { testAssert.init(_model.name, element.fields); assertsQueue.add(testAssert); - }); + } AssertsLauncher(assertsQueue).run((result) { if (result is Success) { diff --git a/lib/app_test_launcher/domain/JsonValueFinder.dart b/lib/app_test_launcher/domain/json_value_finder.dart similarity index 100% rename from lib/app_test_launcher/domain/JsonValueFinder.dart rename to lib/app_test_launcher/domain/json_value_finder.dart diff --git a/lib/app_test_launcher/domain/test_launcher.dart b/lib/app_test_launcher/domain/test_launcher.dart index 3f55fb1..026113f 100644 --- a/lib/app_test_launcher/domain/test_launcher.dart +++ b/lib/app_test_launcher/domain/test_launcher.dart @@ -52,6 +52,9 @@ class TestLauncher { return; } + VariableService.reset(); + VariableService.registerSetup(test.setup.toJson()); + print(""); print("Test: ${test.setup.name}: Start"); _prepare(() async { @@ -75,8 +78,8 @@ class TestLauncher { } } - void _runStep(StepModel step, [bool isNewIteration = true]) async{ - await Future.delayed(Duration(milliseconds: 300)); + void _runStep(StepModel step, [bool isNewIteration = true]) async { + await Future.delayed(Duration(milliseconds: 500)); if (isNewIteration) { _stepTemplate = step.copyWith(); _stepIterationsLeft = step.iterations ?? defaultIterationsCount; @@ -113,33 +116,36 @@ class TestLauncher { _startNewTest(_testTemplate); }); } else { + final failure = result as StepFailure; _stopSession(() { - _onStepError(result as StepFailure); - }); + _onStepError(failure); + }, _extractErrorMessage(failure)); } } void _prepare(Function onSuccess) { print("Test: Prepare"); - VariableService.reset(); _stepFailure = null; _rePersonalize(onSuccess); } void _rePersonalize(Function onSuccess) { - final depersonalize = JSONRPCRequest(TangemSdk.getJsonRpcMethod(TangemSdk.cDepersonalize)!, {}); - final personalize = JSONRPCRequest( - TangemSdk.getJsonRpcMethod(TangemSdk.cPersonalize)!, - _jsonTest.setup.personalizationConfig, - ); + final config = _jsonTest.setup.personalizationConfig; + final personalizationRequest = JSONRPCRequest(TangemSdk.getJsonRpcMethod(TangemSdk.cPersonalize)!, config); + final onPersonalizeCallback = Callback((result) { + print("Test: Prepare: PERSONALIZE complete"); + _stopSession(onSuccess); + }, _onSdkError); + + final depersonalizeRequest = JSONRPCRequest(TangemSdk.getJsonRpcMethod(TangemSdk.cDepersonalize)!, {}); + final onDepersonalizeCallback = Callback((result) { + print("Test: Prepare: DEPERSONALIZE complete"); + TangemSdk.runJSONRPCRequest(onPersonalizeCallback, personalizationRequest); + }, _onSdkError); _startSession(() { print("Test: Prepare: Run: DE/PERSONALIZE"); - TangemSdk.runJSONRPCRequest( - Callback((_) { - TangemSdk.runJSONRPCRequest(Callback((_) => _stopSession(onSuccess), _onSdkError), personalize); - }, _onSdkError), - depersonalize); + TangemSdk.runJSONRPCRequest(onDepersonalizeCallback, depersonalizeRequest); }); } @@ -153,35 +159,44 @@ class TestLauncher { _onStepError(StepFailure failure) { _stepFailure = failure; + final message = _extractErrorMessage(failure); + print(message); + print(jsonEncode(failure.error)); + } + + String _extractErrorMessage(StepFailure failure) { if (failure.error is TangemSdkPluginWrappedError) { + return "TangemSdkPluginWrappedError: ${failure.error.errorMessage}"; } else if (failure.error is TestAssertError) { - print("Assert failure: ${failure.error.runtimeType.toString()}"); - print(jsonEncode(failure.error)); + return "${failure.error.runtimeType.toString()}. ${failure.error.errorMessage}"; } else if (failure.error is TestStepError) { - print("Step failure: ${failure.error.runtimeType.toString()}"); - print(jsonEncode(failure.error)); + return "Step failure: ${failure.error.runtimeType.toString()}"; } else { - print("Some undefined failure: ${failure.error.runtimeType.toString()}"); - print(jsonEncode(failure.error)); + return "Some undefined failure: ${failure.error.runtimeType.toString()}"; } } void _startSession(Function onSuccess) { // print("Starting session..."); - TangemSdk.startSession(Callback((success) { - print("Session started +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - onSuccess(); - }, _onSdkError), { - TangemSdk.initialMessage: _jsonTest.setup.sdkConfig[TangemSdk.initialMessage], - TangemSdk.cardId: _jsonTest.setup.sdkConfig[TangemSdk.cardId], - }); + TangemSdk.startSession( + Callback((success) { + print("Session started +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); + onSuccess(); + }, _onSdkError), + { + TangemSdk.initialMessage: _jsonTest.setup.sdkConfig[TangemSdk.initialMessage], + TangemSdk.cardId: _jsonTest.setup.sdkConfig[TangemSdk.cardId], + }); } - void _stopSession([Function? onSuccess]) { + //TODO: останавливать сессию только при ошибке ассертов + void _stopSession([Function? onSuccess, String? error]) { // print("Stopping session..."); - TangemSdk.stopSession(Callback((success) { - print("Session stopped ---------------------------------------------------------------------"); - onSuccess?.call(); - }, _onSdkError)); + TangemSdk.stopSession( + Callback((success) { + print("Session stopped ---------------------------------------------------------------------"); + onSuccess?.call(); + }, _onSdkError), + error != null ? {"error": error} : {}); } } diff --git a/lib/app_test_launcher/domain/variable_service.dart b/lib/app_test_launcher/domain/variable_service.dart index ddd4a7d..9bda294 100644 --- a/lib/app_test_launcher/domain/variable_service.dart +++ b/lib/app_test_launcher/domain/variable_service.dart @@ -1,22 +1,26 @@ import 'package:devkit/app_test_launcher/domain/common/typedefs.dart'; -import 'JsonValueFinder.dart'; +import 'json_value_finder.dart'; class VariableService { static final _valueFinder = JsonValueFinder(); - static final _stepKey = "#"; - static final _parentStepKey = "#parent"; + static final _targetKey = "#"; + static final _parentTargetKey = "#parent"; static final _actualResult = "actualResult"; static final _error = "error"; + static void registerSetup(SourceMap source) { + _valueFinder.setValue("setup", source); + } + static void registerStep(String name, SourceMap source) { _valueFinder.setValue(name, source); } static void registerActualResult(String name, dynamic result) { - final stepMap = _valueFinder.getValue(name); + final stepMap = _valueFinder.getValue("{$name}"); if (stepMap == null) { // Step is not registered return; @@ -26,7 +30,7 @@ class VariableService { } static void registerError(String name, dynamic result) { - final stepMap = _valueFinder.getValue(name); + final stepMap = _valueFinder.getValue("{$name}"); if (stepMap == null) { // Step is not registered return; @@ -35,23 +39,23 @@ class VariableService { stepMap[_error] = result; } - static dynamic getStepValue(String name, dynamic pointer) { - if (!_valueFinder.canBeInterpret(pointer)) return null; + static dynamic getValue(String name, dynamic pointer) { + if (!_valueFinder.canBeInterpret(pointer)) return pointer; - if (_containsPointer(pointer)) { - final stepPointer = _extractPointer(pointer); - if (stepPointer == null) return null; + if (_containsTarget(pointer)) { + final targetPointer = _extractPointer(pointer); + if (targetPointer == null) return null; - final stepName = stepPointer == _parentStepKey ? name : _extractStepName(stepPointer); - final pathValue = _valueFinder.removeBrackets(pointer).replaceAll("$stepPointer.", ""); - final step = _valueFinder.getValue(stepName); - return step == null ? null : _valueFinder.getValueFrom(pathValue, _valueFinder.getValue("{$step}")); + final targetName = targetPointer == _parentTargetKey ? name : _extractTargetName(targetPointer); + final valuePointer = pointer.replaceAll("$targetPointer.", ""); + final target = _valueFinder.getValue("{$targetName}"); + return target == null ? null : _valueFinder.getValueFrom(valuePointer, target); } else { return _valueFinder.getValueFrom(pointer, _valueFinder.getValue("{$name}")); } } - static String _extractStepName(String stepPointer) => stepPointer.replaceAll(_stepKey, ""); + static String _extractTargetName(String stepPointer) => stepPointer.replaceAll(_targetKey, ""); static String? _extractPointer(String pointer) => _getPrefix(_valueFinder.removeBrackets(pointer)); @@ -60,8 +64,8 @@ class VariableService { return suffixIdx < 0 ? null : value.substring(0, suffixIdx); } - static bool _containsPointer(String? pointer) { - return pointer == null ? false : pointer.indexOf(_stepKey) == 1; + static bool _containsTarget(String? pointer) { + return pointer == null ? false : pointer.indexOf(_targetKey) == 1; } static void reset() { diff --git a/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart b/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart index d8a3ae6..8ea34a1 100644 --- a/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart +++ b/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart @@ -114,7 +114,12 @@ class JsonTestLauncherBloc extends BaseBloc { } void launch(JsonTest jsonTest) { - final launcher = TestLauncher(jsonTest, AssertsFactory()); + final assertsFactory = AssertsFactory().apply((it) { + it.registerAssert(TestAssert.equals, () => EqualsAssert()); + it.registerAssert(TestAssert.isNotEmpty, () => IsNotEmptyAssert()); + it.registerAssert(TestAssert.success, () => SuccessAssert()); + }); + final launcher = TestLauncher(jsonTest, assertsFactory); launcher.onTestComplete = (result) { if (result is Success) { final message = result.name ?? "Unknown test"; diff --git a/lib/main.dart b/lib/main.dart index e71ad2f..b5564d8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,3 +8,5 @@ void main() async { final launcher = App.isDebug ? DebugLauncher() : ReleaseLauncher(); await launcher.launch(); } + +String gWalletPublicKey = ""; \ No newline at end of file diff --git a/test/json_value_finder_test.dart b/test/json_value_finder_test.dart index 5aa6c5c..6c60240 100644 --- a/test/json_value_finder_test.dart +++ b/test/json_value_finder_test.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:devkit/app_test_launcher/domain/JsonValueFinder.dart'; +import 'package:devkit/app_test_launcher/domain/json_value_finder.dart'; import 'package:test/test.dart'; import 'assets.dart'; diff --git a/test/variable_service_test.dart b/test/variable_service_test.dart index 2cd714d..d3941c5 100644 --- a/test/variable_service_test.dart +++ b/test/variable_service_test.dart @@ -22,28 +22,28 @@ void prepareTestForVariableService() { } void findInStep() { - dynamic resultValue = VariableService.getStepValue(_stepName1, "{expectedResult.cardId}"); + dynamic resultValue = VariableService.getValue(_stepName1, "{expectedResult.cardId}"); expect(resultValue, "BB03000000000004"); - resultValue = VariableService.getStepValue(_stepName1, "{#$_stepName1.expectedResult.cardId}"); + resultValue = VariableService.getValue(_stepName1, "{#$_stepName1.expectedResult.cardId}"); expect(resultValue, "BB03000000000004"); - resultValue = VariableService.getStepValue(_stepName2, "{#$_stepName2.expectedResult.cardId}"); + resultValue = VariableService.getValue(_stepName2, "{#$_stepName2.expectedResult.cardId}"); expect(resultValue, "BB03000000000005"); } void findFromAnotherStep() { - dynamic resultValue = VariableService.getStepValue("", "{#$_stepName1.expectedResult.cardId}"); + dynamic resultValue = VariableService.getValue("", "{#$_stepName1.expectedResult.cardId}"); expect(resultValue, "BB03000000000004"); - resultValue = VariableService.getStepValue("any", "{#$_stepName1.expectedResult.cardId}"); + resultValue = VariableService.getValue("any", "{#$_stepName1.expectedResult.cardId}"); expect(resultValue, "BB03000000000004"); - resultValue = VariableService.getStepValue(_stepName2, "{#$_stepName1.expectedResult.cardId}"); + resultValue = VariableService.getValue(_stepName2, "{#$_stepName1.expectedResult.cardId}"); expect(resultValue, "BB03000000000004"); } void findByParent() { // no access to the #parent key from the empty step - dynamic resultValue = VariableService.getStepValue(_stepName1, "{#parent.expectedResult.cardId}"); + dynamic resultValue = VariableService.getValue(_stepName1, "{#parent.expectedResult.cardId}"); expect(resultValue, "BB03000000000004"); - resultValue = VariableService.getStepValue("", "{#parent.expectedResult.cardId}"); + resultValue = VariableService.getValue("", "{#parent.expectedResult.cardId}"); expect(resultValue, null); }