diff --git a/.gitignore b/.gitignore index e5924f5..b021e4e 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,6 @@ scripts/private/ AppDir/ appimage-builder-cache/ AppImageBuilder.yml -android/app/build.gradle \ No newline at end of file +android/app/build.gradle +.env +integration_test/duniter/data/chains/ \ No newline at end of file diff --git a/integration_test/app_test.dart b/integration_test/app_test.dart index a30b1e0..a3f1193 100644 --- a/integration_test/app_test.dart +++ b/integration_test/app_test.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:gecko/globals.dart'; import 'package:gecko/models/widgets_keys.dart'; import 'package:integration_test/integration_test.dart'; import 'package:gecko/main.dart' as app; @@ -13,6 +15,9 @@ void main() { testWidgets('Import chests', (tester) async { app.main(); await tester.pumpAndSettle(const Duration(seconds: 1)); + final ipAddress = dotenv.env['ip_address'] ?? '127.0.0.1'; + + log.d('ip address: $ipAddress'); // Delete all existing chests await deleteAllWallets(tester); diff --git a/integration_test/duniter/data/gecko_tests.json b/integration_test/duniter/data/gecko_tests.json new file mode 100644 index 0000000..f874b5a --- /dev/null +++ b/integration_test/duniter/data/gecko_tests.json @@ -0,0 +1,74 @@ +{ + "first_ud": 1000, + "first_ud_reeval": 100, + "genesis_parameters": { + "genesis_certs_expire_on": 10, + "genesis_certs_min_received": 3, + "genesis_memberships_expire_on": 1051200, + "genesis_smith_certs_expire_on": 2102400, + "genesis_smith_certs_min_received": 3, + "genesis_smith_memberships_expire_on": 1051200 + }, + "identities": { + "test1": { + "balance": 1000, + "certs": ["test2", "test3", "test4"], + "pubkey": "5FeggKqw2AbnGZF9Y9WPM2QTgzENS3Hit94Ewgmzdg5a3LNa" + }, + "test2": { + "balance": 1000, + "certs": ["test1", "test3", "test4"], + "pubkey": "5E4i8vcNjnrDp21Sbnp32WHm2gz8YP3GGFwmdpfg5bHd8Whb" + }, + "test3": { + "balance": 1000, + "certs": ["test1", "test2", "test4"], + "pubkey": "5FhTLzXLNBPmtXtDBFECmD7fvKmTtTQDtvBTfVr97tachA1p" + }, + "test4": { + "balance": 1000, + "certs": ["test1", "test2", "test3"], + "pubkey": "5DXJ4CusmCg8S1yF6JGVn4fxgk5oFx42WctXqHZ17mykgje5" + } + }, + "parameters": { + "babe_epoch_duration": 30, + "cert_period": 15, + "cert_max_by_issuer": 10, + "cert_min_received_cert_to_issue_cert": 2, + "cert_validity_period": 1000, + "idty_confirm_period": 40, + "idty_creation_period": 50, + "membership_period": 1000, + "pending_membership_period": 500, + "ud_creation_period": 10, + "ud_reeval_period": 50, + "smith_cert_period": 15, + "smith_cert_max_by_issuer": 8, + "smith_cert_min_received_cert_to_issue_cert": 2, + "smith_cert_validity_period": 1000, + "smith_membership_period": 1000, + "smith_pending_membership_period": 500, + "smiths_wot_first_cert_issuable_on": 20, + "smiths_wot_min_cert_for_membership": 2, + "wot_first_cert_issuable_on": 20, + "wot_min_cert_for_create_idty_right": 2, + "wot_min_cert_for_membership": 2 + }, + "smiths": { + "test1": { + "certs": ["test2", "test3", "test4"] + }, + "test2": { + "certs": ["test1", "test3", "test4"] + }, + "test3": { + "certs": ["test1", "test2", "test4"] + }, + "test4": { + "certs": ["test1", "test2", "test3"] + } + }, + "sudo_key": "5FeggKqw2AbnGZF9Y9WPM2QTgzENS3Hit94Ewgmzdg5a3LNa", + "technical_committee": ["test1", "test2", "test3"] +} diff --git a/integration_test/duniter/docker-compose.yml b/integration_test/duniter/docker-compose.yml new file mode 100644 index 0000000..5180193 --- /dev/null +++ b/integration_test/duniter/docker-compose.yml @@ -0,0 +1,18 @@ +version: "3.5" + +services: + duniter-v2s-gecko-tests: + container_name: duniter-v2s-gecko-tests + image: duniter/duniter-v2s:debug-latest + command: --sealing=manual + ports: + - "127.0.0.1:9615:9615" + - "127.0.0.1:9933:9933" + - "0.0.0.0:9944:9944" + - "30333:30333" + environment: + DUNITER_INSTANCE_NAME: "gecko_tests" + DUNITER_CHAIN_NAME: "dev" + DUNITER_GENESIS_CONFIG: "/var/lib/duniter/gecko_tests.json" + volumes: + - ./data:/var/lib/duniter diff --git a/integration_test/launch_test.sh b/integration_test/launch_test.sh new file mode 100755 index 0000000..b2f2a51 --- /dev/null +++ b/integration_test/launch_test.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# MP="`dirname \"$0\"`" +# MP="`( cd \"$MP\" && pwd )`" +# cd $MP + +args="$@" + +# Get local IP and set .env +ip_address=$(hostname -I | awk '{print $1}') +echo "ip_address=$ip_address" > .env + +## Start local Duniter node +cd integration_test/duniter +rm -rf data/chains +docker-compose up -d +cd ../.. + +# Start integration test +flutter test integration_test/app_test.dart + +# Stop Duniter +cd integration_test/duniter +docker-compose down + + diff --git a/integration_test/tests_utility.dart b/integration_test/tests_utility.dart index cc5c582..c6fb221 100644 --- a/integration_test/tests_utility.dart +++ b/integration_test/tests_utility.dart @@ -38,6 +38,7 @@ Future waitFor( do { if (DateTime.now().isAfter(end)) { + throw Exception('Timed out waiting for text $text'); } diff --git a/lib/main.dart b/lib/main.dart index c0fd1f1..a6d3cd6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -16,6 +16,7 @@ import 'dart:async'; import 'dart:io'; import 'package:flutter/services.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:gecko/globals.dart'; import 'package:gecko/providers/cesium_plus.dart'; import 'package:gecko/models/chest_data.dart'; @@ -51,6 +52,10 @@ Future main() async { WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); + if (kDebugMode) { + await dotenv.load(); + } + HomeProvider homeProvider = HomeProvider(); // DuniterIndexer _duniterIndexer = DuniterIndexer(); await initHiveForFlutter(); diff --git a/pubspec.lock b/pubspec.lock index d404e72..6fc8368 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -384,6 +384,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.2" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.2" flutter_driver: dependency: "direct main" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 225f635..bdd79c1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -76,6 +76,7 @@ dependencies: dropdown_button2: ^1.6.3 pointycastle: ^3.6.1 hex: ^0.2.0 + flutter_dotenv: ^5.0.2 dev_dependencies: # flutter_launcher_icons: ^0.9.2 @@ -112,3 +113,4 @@ flutter: - assets/onBoarding/progress_bar/ - assets/walletOptions/ - sounds/ + - .env diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index 96c517f..0000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// // This is a basic Flutter widget test. -// // -// // To perform an interaction with a widget in your test, use the WidgetTester -// // utility that Flutter provides. For example, you can send tap and scroll -// // gestures. You can also use WidgetTester to find child widgets in the widget -// // tree, read text, and verify that the values of widget properties are correct. - -// import 'package:flutter/material.dart'; -// import 'package:flutter_test/flutter_test.dart'; - -// import 'package:gecko/main.dart'; - -// void main() { -// testWidgets('Counter increments smoke test', (WidgetTester tester) async { -// // Build our app and trigger a frame. -// await tester.pumpWidget(Gecko()); - -// // Verify that our counter starts at 0. -// expect(find.text('0'), findsOneWidget); -// expect(find.text('1'), findsNothing); - -// // Tap the '+' icon and trigger a frame. -// await tester.tap(find.byIcon(Icons.add)); -// await tester.pump(); - -// // Verify that our counter has incremented. -// expect(find.text('0'), findsNothing); -// expect(find.text('1'), findsOneWidget); -// }); -// } diff --git a/test_driver/app.dart b/test_driver/app.dart deleted file mode 100644 index ec9a318..0000000 --- a/test_driver/app.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:flutter_driver/driver_extension.dart'; -import 'package:gecko/main.dart' as app; - -void main() { - // This line enables the extension. - enableFlutterDriverExtension(); - - // Call the `main()` function of the app, or call `runApp` with - // any widget you are interested in testing. - app.main(); -} diff --git a/test_driver/app_test.dart b/test_driver/app_test.dart deleted file mode 100644 index fa44048..0000000 --- a/test_driver/app_test.dart +++ /dev/null @@ -1,455 +0,0 @@ -import 'dart:async'; -import 'dart:io'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter_driver/flutter_driver.dart'; -import 'package:gecko/globals.dart'; -import 'package:test/test.dart'; -// import 'package:flutter/services.dart'; - -void main() { - int globalTimeout = 2; - group('Gecko App', () { - // First, define the Finders and use them to locate widgets from the - // test suite. Note: the Strings provided to the `byValueKey` method must - // be the same as the Strings we used for the Keys in step 1. - final manageWalletsFinder = find.byValueKey('manageWallets'); - // final buttonFinder = find.byValueKey('increment'); - - FlutterDriver? driver; - String? pinCode; - - // Connect to the Flutter driver before running any tests. - setUpAll(() async { - driver = await FlutterDriver.connect(); - await driver!.waitUntilFirstFrameRasterized(); - }); - - // Close the connection to the driver after the tests have completed. - tearDownAll(() async { - if (driver != null) { - driver!.close(); - } - }); - - // *** Global functions *** // - - // Function to tap the widget by key - Future tapOn(String key) async { - await driver!.tap(find.byValueKey(key)); - } - - // Easy get text - Future getText(String text) async { - return await driver!.getText(find.byValueKey( - text, - )); - } - - // Function to go back to previous screen - Future goBack() async { - await Process.run( - 'adb', - ['shell', 'input', 'keyevent', 'KEYCODE_BACK'], - runInShell: true, - ); - } - - // Easy sleep - Future sleep(int time) async { - await Future.delayed(Duration(milliseconds: time)); - } - - // Test if widget exist on screen, return a boolean - Future isPresent(SerializableFinder byValueKey, - {Duration timeout = const Duration(seconds: 1)}) async { - try { - await driver!.waitFor(byValueKey, timeout: timeout); - return true; - } catch (exception) { - return false; - } - } - - // Create a derivation - Future createDerivation() async { - await tapOn('addDerivation'); - await sleep(300); - } - - // Delete a derivation - Future deleteWallet(bool confirm) async { - await tapOn('deleteWallet'); - await sleep(100); - confirm ? await tapOn('confirmDeleting') : await tapOn('cancelDeleting'); - await sleep(300); - } - - // Delete all wallets - Future deleteAllWallets() async { - await tapOn('drawerMenu'); - await sleep(300); - await tapOn('parameters'); - await sleep(300); - await tapOn('deleteAllWallets'); - await sleep(300); - await tapOn('confirmDeletingAllWallets'); - await sleep(300); - } - - // Fast creation of new Keychain - Future createNewKeychain(String name) async { - await tapOn('drawerMenu'); - await sleep(300); - await tapOn('parameters'); - await sleep(300); - await tapOn('generateKeychain'); - while (await getText('generatedPin') == '') { - log.d('Waiting for pin code generation...'); - await sleep(100); - } - pinCode = await getText('generatedPin'); - await tapOn('storeKeychain'); - await sleep(100); - await driver!.enterText('triche'); - await tapOn('walletName'); - await driver!.enterText(name); - await sleep(50); - await tapOn('confirmStorage'); - await sleep(300); - return pinCode; - } - - // *** Begin of tests *** // - - test('OnBoarding - Open wallets management', ( - {timeout = Timeout.none}) async { - // await driver.runUnsynchronized(() async { // Needed if we want to manage async drivers - await driver!.tap(manageWalletsFinder); - - // If a wallet exist, go to delete theme all - if (!await isPresent(find.byValueKey('goStep1'))) { - await goBack(); - - await deleteAllWallets(); - - await driver!.tap(manageWalletsFinder); - } - - // Get the SerializableFinder for text widget with key 'textOnboarding' - SerializableFinder textOnboarding = find.byValueKey( - 'textOnboarding', - ); - - await sleep(100); - - // Verify onboarding is starting, with text - expect(await driver!.getText(textOnboarding), - "Je ne connais pour l’instant aucun de vos portefeuilles.\n\nVous pouvez en créer un nouveau, ou bien importer un portefeuille Cesium existant."); - }); - - test('OnBoarding - Go to create restore sentance', ( - {timeout = Timeout.none}) async { - await tapOn('goStep1'); - await tapOn('goStep2'); - await tapOn('goStep3'); - await tapOn('goStep4'); - await tapOn('goStep5'); - await tapOn('goStep6'); - - expect( - await driver!.getText(find.byValueKey( - 'step6', - )), - "iGeneratedYourMnemonicKeepItSecret".tr()); - }); - - test('OnBoarding - Generate sentance and confirme it', ( - {timeout = Timeout.none}) async { - await tapOn('goStep7'); - - while (await getText('word1') == '...') { - log.d('Waiting for Mnemonic generation...'); - await sleep(100); - } - - Future selectWord() async { - List words = [for (var i = 1; i <= 13; i += 1) i]; - - for (var j = 1; j < 13; j++) { - words[j] = await getText('word$j'); - } - expect( - await getText('step7'), "C'est le moment de noter votre phrase !"); - - await tapOn('goStep8'); - await sleep(200); - - String goodWord = words[int.parse( - await getText('askedWord'), - )]; - - // Enter the expected word - await driver!.enterText(goodWord); - - // Check if word is valid - await driver!.waitFor(find.text("C'est le bon mot !")); - - // Continue onboarding workflow - await tapOn('goStep9'); - } - - await selectWord(); - - //Go back 2 times to mnemonic generation screen - await goBack(); - await goBack(); - await sleep(100); - - // Generate 3 times mnemonic - await tapOn('generateMnemonic'); - await tapOn('generateMnemonic'); - await tapOn('generateMnemonic'); - await sleep(500); - - await selectWord(); - }); - test('OnBoarding - Generate secret code and confirm it', ( - {timeout = Timeout.none}) async { - expect(await getText('step9'), - "Super !\n\nJe vais maintenant créer votre code secret. \n\nVotre code secret chiffre votre coffre de clefs, ce qui le rend inutilisable par d’autres, par exemple si vous perdez votre téléphone ou si on vous le vole."); - await sleep(800); - await tapOn('goStep10'); - await sleep(50); - await tapOn('goStep11'); - - while (await getText('generatedPin') == '') { - log.d('Waiting for pin code generation...'); - await sleep(100); - } - - // Change secret code 4 times - for (int i = 0; i < 4; i++) { - await tapOn('changeSecretCode'); - } - - await sleep(500); - pinCode = await getText('generatedPin'); - - await tapOn('goStep12'); - await sleep(300); - - // //Enter bad secret code - // await driver.enterText('abcde'); - // await tapOn('formKey'); - // await sleep(1500); - // await tapOn('formKey2'); - - //Enter good secret code - await driver!.enterText(pinCode!); - - expect(await getText('step13'), - "Top !\n\nVotre coffre et votre portefeuille ont été créés avec un immense succès.\n\nFélicitations !"); - }); - - test('My wallets - Rename first derivation', ( - {timeout = const Duration(seconds: 2)}) async { - await tapOn('goWalletHome'); - - expect(await getText('myWallets'), "geckoChest".tr()); - await sleep(300); - - // Go to first derivation and rename it - await driver!.tap(find.text('Mon portefeuille courant')); - await sleep(300); - await tapOn('renameWallet'); - await sleep(100); - await tapOn('walletName'); - await sleep(100); - await driver!.enterText('Renommage wallet 1'); - await sleep(300); - await tapOn('renameWallet'); - await sleep(400); - await driver!.waitFor(find.text('Renommage wallet 1'), timeout: timeout); - // expect(await getText('walletName'), "Renommage wallet 1"); - await goBack(); - }); - - test('My wallets - Create a derivations, open thems, tap all buttons', ( - {timeout = const Duration(seconds: 2)}) async { - await driver!.waitFor(find.text('Renommage wallet 1'), timeout: timeout); - // Add a second derivation - await createDerivation(); - - // Go to second derivation options - await driver!.tap(find.text('Portefeuille 2')); - await sleep(100); - - // Test options - await tapOn('displayBalance'); - await tapOn('displayHistory'); - await sleep(300); - await goBack(); - await tapOn('displayBalance'); - await sleep(100); - await tapOn('displayBalance'); - await sleep(100); - await tapOn('displayBalance'); - await tapOn('setDefaultWallet'); - await sleep(50); - await tapOn('copyPubkey'); - await driver!.waitFor(find - .text('Cette clé publique a été copié dans votre presse-papier.')); - await goBack(); - - // Add a third derivation - await createDerivation(); - - // Add a fourth derivation - await createDerivation(); - await sleep(50); - - // Go to third derivation options - await driver!.tap(find.text('Portefeuille 3')); - await sleep(100); - await tapOn('displayBalance'); - - // Delete third derivation - await deleteWallet(true); - }); - - test('My wallets - Extra tests', ( - {timeout = const Duration(seconds: 2)}) async { - // Add derivation 5,6 and 7 - await driver!.waitFor(find.text('Portefeuille 4'), timeout: timeout); - await createDerivation(); - await createDerivation(); - await createDerivation(); - - // Go home and come back to my wallets view - await goBack(); - await sleep(100); - await tapOn('manageWallets'); - await sleep(200); - //Enter secret code - await driver!.enterText(pinCode!); - await sleep(200); - - // Go to derivation 6 and delete it - await driver!.tap(find.text('Portefeuille 6')); - await sleep(100); - await deleteWallet(true); - - // Go to 2nd derivation and check if it's de default - await driver!.tap(find.text('Portefeuille 2')); - await driver!.waitFor(find.text('Ce portefeuille est celui par defaut')); - await tapOn('setDefaultWallet'); - await sleep(100); - await driver!.waitFor(find.text('Ce portefeuille est celui par defaut')); - await sleep(300); - - // Display history, copy pubkey, go back and rename wallet name - await tapOn('displayHistory'); - await sleep(400); - await tapOn('copyPubkey'); - await driver!.waitFor(find - .text('Cette clé publique a été copié dans votre presse-papier.')); - await sleep(800); - await goBack(); - await sleep(300); - await tapOn('renameWallet'); - await sleep(100); - await tapOn('walletName'); - await sleep(100); - await driver!.enterText('Renommage wallet 2'); - await sleep(300); - await tapOn('renameWallet'); - await sleep(400); - await goBack(); - await driver!.waitFor(find.text('Renommage wallet 2')); - await driver!.scrollIntoView(find.text('+')); - await createDerivation(); - await createDerivation(); - await driver!.scrollIntoView(find.text('+')); - await createDerivation(); - await createDerivation(); - await driver!.scrollIntoView(find.text('+')); - await createDerivation(); - await createDerivation(); - await driver!.scrollIntoView(find.text('+')); - await createDerivation(); - await createDerivation(); - await driver!.scrollIntoView(find.text('+')); - await createDerivation(); - await createDerivation(); - await driver!.scrollIntoView(find.text('+')); - await createDerivation(); - await createDerivation(); - await driver!.scrollIntoView(find.text('+')); - await createDerivation(); - await sleep(400); - - // Scroll the wallet screen until Derivation 20 and open it - await driver!.scrollUntilVisible( - find.byValueKey('listWallets'), - find.text('Portefeuille 20'), - dyScroll: -300.0, - ); - - await driver!.waitFor(find.text('Portefeuille 20')); - await sleep(400); - await driver!.tap(find.text('Portefeuille 20')); - await tapOn('copyPubkey'); - }); - - test('Search - Search Pi profile, navigate in history transactions', ( - {timeout = const Duration(seconds: 2)}) async { - await driver!.waitFor(find.text('Portefeuille 20'), timeout: timeout); - await goBack(); - await goBack(); - await sleep(200); - await tapOn('searchIcon'); - await sleep(400); - await driver!.enterText('D2meevcAHFTS2gQMvmRW5Hzi25jDdikk4nC4u1FkwRaU'); - await sleep(100); - await tapOn('copyPubkey'); - await sleep(500); - await tapOn('switchPayHistory'); - await sleep(1200); - // await driver.scrollIntoView(find.byValueKey('listTransactions')); - await driver!.scrollUntilVisible( - find.byValueKey('listTransactions'), - find.byValueKey('transaction35'), - dyScroll: -600.0, - ); - await sleep(100); - await tapOn('transaction33'); - await driver!.waitFor(find.text('Commentaire:')); - - // Want to paste pubkey copied, but doesn't work actualy with flutter driver: https://github.com/flutter/flutter/issues/47448 - // final ClipboardData pubkeyCopied = - // await Clipboard.getData(Clipboard.kTextPlain); - // await driver.enterText(pubkeyCopied.text); - - await sleep(300); - }, timeout: Timeout(Duration(minutes: globalTimeout))); - - test('Wallet generation - Fast wallets generations', ( - {timeout = const Duration(seconds: 2)}) async { - await driver!.waitFor(find.text('Commentaire:'), timeout: timeout); - await goBack(); - await goBack(); - await deleteAllWallets(); - await sleep(100); - final String? pincode = await (createNewKeychain('Fast wallet')); - await sleep(200); - await driver!.enterText(pincode!); - await sleep(100); - await createDerivation(); - await sleep(100); - await driver!.tap(find.text('Fast wallet')); - await driver!.waitFor(find.text('Fast wallet')); - // Wait 3 seconds at the end - await sleep(3000); - }); - }, timeout: Timeout(Duration(minutes: globalTimeout))); -}