From 42de2a43957fb4761a61db2192612d3b9c4d7d7f Mon Sep 17 00:00:00 2001 From: Mati Date: Fri, 6 Oct 2023 15:13:42 -0300 Subject: [PATCH 01/10] feat(login page): wallet address in login page PoC PE-4752 --- .../login/blocs/login_bloc.dart | 13 +++++++++++ .../login/views/login_page.dart | 22 +++++++++++++++++-- lib/user/repositories/user_repository.dart | 12 ++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/lib/authentication/login/blocs/login_bloc.dart b/lib/authentication/login/blocs/login_bloc.dart index 1df9402f70..d9f7644e67 100644 --- a/lib/authentication/login/blocs/login_bloc.dart +++ b/lib/authentication/login/blocs/login_bloc.dart @@ -5,11 +5,13 @@ import 'package:ardrive/authentication/ardrive_auth.dart'; import 'package:ardrive/entities/profile_types.dart'; import 'package:ardrive/services/arconnect/arconnect.dart'; import 'package:ardrive/services/arconnect/arconnect_wallet.dart'; +import 'package:ardrive/user/repositories/user_repository.dart'; import 'package:ardrive/user/user.dart'; import 'package:ardrive/utils/html/html_util.dart'; import 'package:ardrive/utils/logger/logger.dart'; import 'package:ardrive_io/ardrive_io.dart'; import 'package:arweave/arweave.dart'; +import 'package:arweave/utils.dart'; import 'package:bip39/bip39.dart' as bip39; import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; @@ -25,6 +27,7 @@ part 'login_state.dart'; class LoginBloc extends Bloc { final ArDriveAuth _arDriveAuth; final ArConnectService _arConnectService; + final UserRepository _userRepository; bool ignoreNextWaletSwitch = false; @@ -34,11 +37,21 @@ class LoginBloc extends Bloc { @visibleForTesting ProfileType? profileType; + Future getWalletAddress() async { + final owner = await _userRepository.getOwnerOfDefaultProfile(); + if (owner == null) { + return null; + } + return ownerToAddress(owner); + } + LoginBloc({ required ArDriveAuth arDriveAuth, required ArConnectService arConnectService, + required UserRepository userRepository, }) : _arDriveAuth = arDriveAuth, _arConnectService = arConnectService, + _userRepository = userRepository, super(LoginLoading()) { on(_onLoginEvent); _listenToWalletChange(); diff --git a/lib/authentication/login/views/login_page.dart b/lib/authentication/login/views/login_page.dart index cc23cadfde..f2eea04bea 100644 --- a/lib/authentication/login/views/login_page.dart +++ b/lib/authentication/login/views/login_page.dart @@ -9,12 +9,14 @@ import 'package:ardrive/authentication/login/blocs/stub_web_wallet.dart' // stub if (dart.library.html) 'package:ardrive/authentication/login/blocs/web_wallet.dart'; import 'package:ardrive/blocs/profile/profile_cubit.dart'; import 'package:ardrive/components/app_version_widget.dart'; +import 'package:ardrive/components/truncated_address.dart'; import 'package:ardrive/misc/resources.dart'; import 'package:ardrive/pages/drive_detail/components/hover_widget.dart'; import 'package:ardrive/services/arconnect/arconnect.dart'; import 'package:ardrive/services/authentication/biometric_authentication.dart'; import 'package:ardrive/services/authentication/biometric_permission_dialog.dart'; import 'package:ardrive/services/config/config_service.dart'; +import 'package:ardrive/user/repositories/user_repository.dart'; import 'package:ardrive/utils/app_localizations_wrapper.dart'; import 'package:ardrive/utils/app_platform.dart'; import 'package:ardrive/utils/io_utils.dart'; @@ -48,6 +50,7 @@ class _LoginPageState extends State { create: (context) => LoginBloc( arConnectService: ArConnectService(), arDriveAuth: context.read(), + userRepository: context.read(), )..add(const CheckIfUserIsLoggedIn()), child: BlocConsumer( listener: (context, state) { @@ -314,7 +317,9 @@ class _LoginPageScaffoldState extends State { } else if (enableSeedPhraseLogin && state is LoginDownloadGeneratedWallet) { content = DownloadWalletView( - mnemonic: state.mnemonic, wallet: state.walletFile); + mnemonic: state.mnemonic, + wallet: state.walletFile, + ); } else { content = PromptWalletView( key: const Key('promptWalletView'), @@ -684,6 +689,19 @@ class _PromptPasswordViewState extends State { textAlign: TextAlign.center, style: ArDriveTypography.headline.headline4Bold(), ), + FutureBuilder( + future: context.read().getWalletAddress(), + builder: + (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData) { + return TruncatedAddress( + walletAddress: snapshot.data!, + ); + } else { + return const SizedBox.shrink(); + } + }, + ), Column( children: [ ArDriveTextField( @@ -2243,7 +2261,7 @@ class CreateNewWalletViewState extends State { color: colors.themeErrorMuted, ) .copyWith(fontSize: 14))) - ]), + ]), shape: RoundedRectangleBorder( side: BorderSide( color: colors.themeErrorMuted, width: 1), diff --git a/lib/user/repositories/user_repository.dart b/lib/user/repositories/user_repository.dart index f02f5d8642..718ca7bf86 100644 --- a/lib/user/repositories/user_repository.dart +++ b/lib/user/repositories/user_repository.dart @@ -14,6 +14,7 @@ abstract class UserRepository { Wallet wallet, ); Future deleteUser(); + Future getOwnerOfDefaultProfile(); factory UserRepository(ProfileDao profileDao, ArweaveService arweave) => _UserRepository( @@ -86,6 +87,17 @@ class _UserRepository implements UserRepository { return profile != null; } + + @override + Future getOwnerOfDefaultProfile() async { + final profile = await _profileDao.getDefaultProfile(); + + if (profile == null) { + return null; + } + + return profile.walletPublicKey; + } } class NoProfileFoundException implements Exception { From a31f8b12a6465239af418e66e5ffb5c1d93dbca9 Mon Sep 17 00:00:00 2001 From: Mati Date: Fri, 6 Oct 2023 15:27:02 -0300 Subject: [PATCH 02/10] feat(login page): enlages the font PE-4752 --- lib/authentication/login/views/login_page.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/authentication/login/views/login_page.dart b/lib/authentication/login/views/login_page.dart index f2eea04bea..5ea16f8ea8 100644 --- a/lib/authentication/login/views/login_page.dart +++ b/lib/authentication/login/views/login_page.dart @@ -696,6 +696,7 @@ class _PromptPasswordViewState extends State { if (snapshot.hasData) { return TruncatedAddress( walletAddress: snapshot.data!, + fontSize: 18, ); } else { return const SizedBox.shrink(); From e4809eb1665c52e7a98ed05a13c2067a48a1ac41 Mon Sep 17 00:00:00 2001 From: Mati Date: Fri, 6 Oct 2023 17:15:01 -0300 Subject: [PATCH 03/10] feat(login page): minor fix; add localization PE-4752 --- .../login/views/login_page.dart | 19 ++++++++++++++++--- lib/l10n/app_en.arb | 6 +++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/authentication/login/views/login_page.dart b/lib/authentication/login/views/login_page.dart index 5ea16f8ea8..6952d6636d 100644 --- a/lib/authentication/login/views/login_page.dart +++ b/lib/authentication/login/views/login_page.dart @@ -694,9 +694,22 @@ class _PromptPasswordViewState extends State { builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData) { - return TruncatedAddress( - walletAddress: snapshot.data!, - fontSize: 18, + return Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + appLocalizationsOf(context).walletAddress, + style: ArDriveTypography.body + .captionRegular() + .copyWith(fontSize: 18), + ), + const SizedBox(width: 8), + TruncatedAddress( + walletAddress: snapshot.data!, + fontSize: 18, + ), + ], ); } else { return const SizedBox.shrink(); diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index e17d8cbcd9..fed602f6d3 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1970,6 +1970,10 @@ "@waitForDownload": { "description": "User must to wait this download" }, + "walletAddress": "Wallet address:", + "@walletAddress": { + "description": "E.g. \"The wallet address is… ABCDEFGH\"" + }, "walletChangedDuringManifestCreation": "Provided wallet has unexpectedly changed during manifest creation...", "@walletChangedDuringManifestCreation": { "description": "The wallet has been changed while the creation of the manifest was in process" @@ -2078,4 +2082,4 @@ "@zippingYourFiles": { "description": "Download failure message when a file is too big" } -} \ No newline at end of file +} From ffeeea0a36a1e7b43ae2a8d8d3c614578aa0cbaa Mon Sep 17 00:00:00 2001 From: Mati Date: Fri, 6 Oct 2023 18:22:52 -0300 Subject: [PATCH 04/10] test(login bloc): fixes broken unit testss PE-4752 --- .../login/blocs/login_bloc_test.dart | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/authentication/login/blocs/login_bloc_test.dart b/test/authentication/login/blocs/login_bloc_test.dart index 08d7b36d66..6e65a45347 100644 --- a/test/authentication/login/blocs/login_bloc_test.dart +++ b/test/authentication/login/blocs/login_bloc_test.dart @@ -2,6 +2,7 @@ import 'package:ardrive/authentication/ardrive_auth.dart'; import 'package:ardrive/authentication/login/blocs/login_bloc.dart'; import 'package:ardrive/entities/profile_types.dart'; import 'package:ardrive/services/services.dart'; +import 'package:ardrive/user/repositories/user_repository.dart'; import 'package:ardrive/user/user.dart'; import 'package:ardrive_io/ardrive_io.dart'; import 'package:bloc_test/bloc_test.dart'; @@ -15,6 +16,7 @@ import '../../../test_utils/utils.dart'; void main() { late ArDriveAuth mockArDriveAuth; late ArConnectService mockArConnectService; + late UserRepository mockUserRepository; final wallet = getTestWallet(); @@ -24,6 +26,7 @@ void main() { setUp(() { mockArDriveAuth = MockArDriveAuth(); mockArConnectService = MockArConnectService(); + mockUserRepository = MockUserRepository(); }); group('AddWalletFile', () { @@ -38,6 +41,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -62,6 +66,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -103,6 +108,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -130,6 +136,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -160,6 +167,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -197,6 +205,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -236,6 +245,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -257,6 +267,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -290,6 +301,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -315,6 +327,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -353,6 +366,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -376,6 +390,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -423,6 +438,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -449,6 +465,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -483,6 +500,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -512,6 +530,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -557,6 +576,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -581,6 +601,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -606,6 +627,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -642,6 +664,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -665,6 +688,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -698,6 +722,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, act: (bloc) async { @@ -729,6 +754,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { @@ -754,6 +780,7 @@ void main() { return LoginBloc( arDriveAuth: mockArDriveAuth, arConnectService: mockArConnectService, + userRepository: mockUserRepository, ); }, setUp: () { From 7f293297c9285c708b5fdf29c9ed357c1d6f46c6 Mon Sep 17 00:00:00 2001 From: Mati Date: Mon, 9 Oct 2023 12:26:07 -0300 Subject: [PATCH 05/10] feat(ardrive auth): moves the getWalletAddress out of the login bloc PE-4752 --- lib/authentication/ardrive_auth.dart | 11 +++++++++++ lib/authentication/login/blocs/login_bloc.dart | 11 ----------- lib/authentication/login/views/login_page.dart | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/authentication/ardrive_auth.dart b/lib/authentication/ardrive_auth.dart index 1a9b45cd13..22bc046615 100644 --- a/lib/authentication/ardrive_auth.dart +++ b/lib/authentication/ardrive_auth.dart @@ -12,6 +12,7 @@ import 'package:ardrive/utils/logger/logger.dart'; import 'package:ardrive/utils/metadata_cache.dart'; import 'package:ardrive/utils/secure_key_value_store.dart'; import 'package:arweave/arweave.dart'; +import 'package:arweave/utils.dart'; import 'package:cryptography/cryptography.dart'; import 'package:flutter/foundation.dart'; import 'package:stash_shared_preferences/stash_shared_preferences.dart'; @@ -29,6 +30,7 @@ abstract class ArDriveAuth { User get currentUser; Stream onAuthStateChanged(); Future isBiometricsEnabled(); + Future getWalletAddress(); factory ArDriveAuth({ required ArweaveService arweave, @@ -326,6 +328,15 @@ class ArDriveAuthImpl implements ArDriveAuth { return firstPrivateDriveTxId; } + + @override + Future getWalletAddress() async { + final owner = await _userRepository.getOwnerOfDefaultProfile(); + if (owner == null) { + return null; + } + return ownerToAddress(owner); + } } class AuthenticationFailedException implements Exception { diff --git a/lib/authentication/login/blocs/login_bloc.dart b/lib/authentication/login/blocs/login_bloc.dart index d9f7644e67..ab944e2ff4 100644 --- a/lib/authentication/login/blocs/login_bloc.dart +++ b/lib/authentication/login/blocs/login_bloc.dart @@ -11,7 +11,6 @@ import 'package:ardrive/utils/html/html_util.dart'; import 'package:ardrive/utils/logger/logger.dart'; import 'package:ardrive_io/ardrive_io.dart'; import 'package:arweave/arweave.dart'; -import 'package:arweave/utils.dart'; import 'package:bip39/bip39.dart' as bip39; import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; @@ -27,7 +26,6 @@ part 'login_state.dart'; class LoginBloc extends Bloc { final ArDriveAuth _arDriveAuth; final ArConnectService _arConnectService; - final UserRepository _userRepository; bool ignoreNextWaletSwitch = false; @@ -37,21 +35,12 @@ class LoginBloc extends Bloc { @visibleForTesting ProfileType? profileType; - Future getWalletAddress() async { - final owner = await _userRepository.getOwnerOfDefaultProfile(); - if (owner == null) { - return null; - } - return ownerToAddress(owner); - } - LoginBloc({ required ArDriveAuth arDriveAuth, required ArConnectService arConnectService, required UserRepository userRepository, }) : _arDriveAuth = arDriveAuth, _arConnectService = arConnectService, - _userRepository = userRepository, super(LoginLoading()) { on(_onLoginEvent); _listenToWalletChange(); diff --git a/lib/authentication/login/views/login_page.dart b/lib/authentication/login/views/login_page.dart index 6952d6636d..5d07b76948 100644 --- a/lib/authentication/login/views/login_page.dart +++ b/lib/authentication/login/views/login_page.dart @@ -690,7 +690,7 @@ class _PromptPasswordViewState extends State { style: ArDriveTypography.headline.headline4Bold(), ), FutureBuilder( - future: context.read().getWalletAddress(), + future: context.read().getWalletAddress(), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData) { From 756f7141bbff1adaa57dc4d6ebfa456d8449b3af Mon Sep 17 00:00:00 2001 From: Mati Date: Mon, 9 Oct 2023 12:26:45 -0300 Subject: [PATCH 06/10] chore(user repository): adds comments PE-4752 --- lib/user/repositories/user_repository.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/user/repositories/user_repository.dart b/lib/user/repositories/user_repository.dart index 718ca7bf86..573b1cab88 100644 --- a/lib/user/repositories/user_repository.dart +++ b/lib/user/repositories/user_repository.dart @@ -34,6 +34,8 @@ class _UserRepository implements UserRepository { _arweave = arweave; // TODO: Check ProfileDAO to implement only one source for user data + + // Will return null if no user is not logged in - i.e. not present in the DB @override Future getUser(String password) async { final profile = await _profileDao.getDefaultProfile(); @@ -88,6 +90,7 @@ class _UserRepository implements UserRepository { return profile != null; } + // Will return null if no user is not logged in - i.e. not present in the DB @override Future getOwnerOfDefaultProfile() async { final profile = await _profileDao.getDefaultProfile(); From 1f18bff7ea1dc2bfbb46120d487bd2257ea5a400 Mon Sep 17 00:00:00 2001 From: Mati Date: Tue, 10 Oct 2023 13:25:28 -0300 Subject: [PATCH 07/10] test(user repository): re-structores tests in groups; adds a unit test for getOwnerOfDefaultProfile PE-4752 --- .../repositories/user_repository_test.dart | 304 ++++++++++-------- 1 file changed, 171 insertions(+), 133 deletions(-) diff --git a/test/user/repositories/user_repository_test.dart b/test/user/repositories/user_repository_test.dart index 388a6aa3d7..2d8512e943 100644 --- a/test/user/repositories/user_repository_test.dart +++ b/test/user/repositories/user_repository_test.dart @@ -38,162 +38,200 @@ void main() { registerFallbackValue(emptyListOfTranscations); }); - group('testing getUser method', () { - setUp(() { - when(() => mockProfileDao.getDefaultProfile()) - .thenAnswer((_) async => Profile( - encryptedPublicKey: Uint8List.fromList([]), - encryptedWallet: Uint8List.fromList([]), - keySalt: Uint8List.fromList([]), - profileType: 0, //json - username: '', + group('UserRepository class', () { + group('getUser method', () { + setUp(() { + when(() => mockProfileDao.getDefaultProfile()) + .thenAnswer((_) async => Profile( + encryptedPublicKey: Uint8List.fromList([]), + encryptedWallet: Uint8List.fromList([]), + keySalt: Uint8List.fromList([]), + profileType: 0, //json + username: '', + walletPublicKey: '', + id: 'id', + )); + when(() => mockProfileDao.loadDefaultProfile(rightPassword)) + .thenAnswer((_) async => Future.value(ProfileLoadDetails( + wallet: wallet, walletPublicKey: '', - id: 'id', - )); - when(() => mockProfileDao.loadDefaultProfile(rightPassword)) - .thenAnswer((_) async => Future.value(ProfileLoadDetails( - wallet: wallet, - walletPublicKey: '', - key: SecretKey([1, 2, 3]), - details: Profile( - encryptedPublicKey: Uint8List.fromList([]), - encryptedWallet: Uint8List.fromList([]), - keySalt: Uint8List.fromList([]), - profileType: 0, //json - username: '', - walletPublicKey: '', - id: 'id', - )))); - when(() => mockArweaveService.getWalletBalance(any())) - .thenAnswer((_) async => BigInt.zero); + key: SecretKey([1, 2, 3]), + details: Profile( + encryptedPublicKey: Uint8List.fromList([]), + encryptedWallet: Uint8List.fromList([]), + keySalt: Uint8List.fromList([]), + profileType: 0, //json + username: '', + walletPublicKey: '', + id: 'id', + )))); + when(() => mockArweaveService.getWalletBalance(any())) + .thenAnswer((_) async => BigInt.zero); + }); + + test('should return a user with the same information of the profile', + () async { + final result = await userRepository.getUser(rightPassword); + + final userToMatch = User( + password: rightPassword, + wallet: wallet, + walletAddress: await wallet.getAddress(), + walletBalance: BigInt.zero, + cipherKey: SecretKey([1, 2, 3]), + profileType: ProfileType.json, + ); + + expect(result, isNotNull); + expect(result!.password, userToMatch.password); + expect(result.wallet, userToMatch.wallet); + expect(result.walletAddress, await wallet.getAddress()); + expect(result.walletBalance, userToMatch.walletBalance); + expect(result.cipherKey, userToMatch.cipherKey); + expect(result.profileType, userToMatch.profileType); + verify(() => mockProfileDao.loadDefaultProfile(rightPassword)) + .called(1); + }); + + test('should return null if there is no profile', () async { + when(() => mockProfileDao.getDefaultProfile()) + .thenAnswer((_) async => Future.value(null)); + + final result = await userRepository.getUser(rightPassword); + + expect(result, isNull); + verify(() => mockProfileDao.getDefaultProfile()).called(1); + verifyNever(() => mockProfileDao.loadDefaultProfile(rightPassword)); + }); }); - test('should return a user with the same information of the profile', - () async { - final result = await userRepository.getUser(rightPassword); - - final userToMatch = User( - password: rightPassword, - wallet: wallet, - walletAddress: await wallet.getAddress(), - walletBalance: BigInt.zero, - cipherKey: SecretKey([1, 2, 3]), - profileType: ProfileType.json, - ); - - expect(result, isNotNull); - expect(result!.password, userToMatch.password); - expect(result.wallet, userToMatch.wallet); - expect(result.walletAddress, await wallet.getAddress()); - expect(result.walletBalance, userToMatch.walletBalance); - expect(result.cipherKey, userToMatch.cipherKey); - expect(result.profileType, userToMatch.profileType); - verify(() => mockProfileDao.loadDefaultProfile(rightPassword)).called(1); - }); + group('hasUser method', () { + test('should return true if there is a profile', () async { + when(() => mockProfileDao.getDefaultProfile()) + .thenAnswer((_) async => Future.value(Profile( + encryptedPublicKey: Uint8List.fromList([]), + encryptedWallet: Uint8List.fromList([]), + keySalt: Uint8List.fromList([]), + profileType: 0, //json + username: '', + walletPublicKey: '', + id: 'id', + ))); - test('should return null if there is no profile', () async { - when(() => mockProfileDao.getDefaultProfile()) - .thenAnswer((_) async => Future.value(null)); + final result = await userRepository.hasUser(); - final result = await userRepository.getUser(rightPassword); + expect(result, true); - expect(result, isNull); - verify(() => mockProfileDao.getDefaultProfile()).called(1); - verifyNever(() => mockProfileDao.loadDefaultProfile(rightPassword)); - }); - }); + verify(() => mockProfileDao.getDefaultProfile()).called(1); + }); - group('testing hasUser method', () { - test('should return true if there is a profile', () async { - when(() => mockProfileDao.getDefaultProfile()) - .thenAnswer((_) async => Future.value(Profile( - encryptedPublicKey: Uint8List.fromList([]), - encryptedWallet: Uint8List.fromList([]), - keySalt: Uint8List.fromList([]), - profileType: 0, //json - username: '', - walletPublicKey: '', - id: 'id', - ))); + test('should return false if there is a profile', () async { + when(() => mockProfileDao.getDefaultProfile()) + .thenAnswer((_) async => Future.value(null)); - final result = await userRepository.hasUser(); + final result = await userRepository.hasUser(); - expect(result, true); + expect(result, false); + + verify(() => mockProfileDao.getDefaultProfile()).called(1); + }); + }); - verify(() => mockProfileDao.getDefaultProfile()).called(1); + group('saveUser method', () { + test('should save the user', () async { + final user = User( + password: rightPassword, + wallet: wallet, + walletAddress: await wallet.getAddress(), + walletBalance: BigInt.zero, + cipherKey: SecretKey([1, 2, 3]), + profileType: ProfileType.json, + ); + + when(() => mockProfileDao.addProfile( + 'user.username', rightPassword, wallet, user.profileType)) + .thenAnswer((_) async => Future.value(SecretKey([1, 2, 3]))); + + await userRepository.saveUser( + rightPassword, + user.profileType, + user.wallet, + ); + + verify(() => mockProfileDao.addProfile( + 'user.username', rightPassword, wallet, user.profileType)) + .called(1); + }); }); - test('should return false if there is a profile', () async { - when(() => mockProfileDao.getDefaultProfile()) - .thenAnswer((_) async => Future.value(null)); + group('deleteUser method', () { + test('should delete the user when has user', () async { + when(() => mockProfileDao.deleteProfile()) + .thenAnswer((_) async => Future.value()); + when(() => mockProfileDao.getDefaultProfile()).thenAnswer( + (_) async => Future.value( + Profile( + encryptedPublicKey: Uint8List.fromList([]), + encryptedWallet: Uint8List.fromList([]), + keySalt: Uint8List.fromList([]), + profileType: 0, //json + username: '', + walletPublicKey: '', + id: 'id', + ), + ), + ); - final result = await userRepository.hasUser(); + await userRepository.deleteUser(); - expect(result, false); + verify(() => mockProfileDao.getDefaultProfile()).called(1); + verify(() => mockProfileDao.deleteProfile()).called(1); + }); - verify(() => mockProfileDao.getDefaultProfile()).called(1); - }); - }); + test('should do nothing when there is no user ', () async { + when(() => mockProfileDao.deleteProfile()) + .thenAnswer((_) async => Future.value()); + when(() => mockProfileDao.getDefaultProfile()) + .thenAnswer((_) async => Future.value(null)); - group('testing saveUser method', () { - test('should save the user', () async { - final user = User( - password: rightPassword, - wallet: wallet, - walletAddress: await wallet.getAddress(), - walletBalance: BigInt.zero, - cipherKey: SecretKey([1, 2, 3]), - profileType: ProfileType.json, - ); - - when(() => mockProfileDao.addProfile( - 'user.username', rightPassword, wallet, user.profileType)) - .thenAnswer((_) async => Future.value(SecretKey([1, 2, 3]))); - - await userRepository.saveUser( - rightPassword, - user.profileType, - user.wallet, - ); - - verify(() => mockProfileDao.addProfile( - 'user.username', rightPassword, wallet, user.profileType)).called(1); + await userRepository.deleteUser(); + + verifyNever(() => mockProfileDao.deleteProfile()); + }); }); - }); - group('testing deleteUser method', () { - test('should delete the user when has user', () async { - when(() => mockProfileDao.deleteProfile()) - .thenAnswer((_) async => Future.value()); - when(() => mockProfileDao.getDefaultProfile()).thenAnswer( - (_) async => Future.value( - Profile( - encryptedPublicKey: Uint8List.fromList([]), - encryptedWallet: Uint8List.fromList([]), - keySalt: Uint8List.fromList([]), - profileType: 0, //json - username: '', - walletPublicKey: '', - id: 'id', + group('getOwnerOfDefaultProfile method', () { + test('should return the walletPublicKey when there is a profile', + () async { + when(() => mockProfileDao.getDefaultProfile()).thenAnswer( + (_) async => Future.value( + Profile( + encryptedPublicKey: Uint8List.fromList([]), + encryptedWallet: Uint8List.fromList([]), + keySalt: Uint8List.fromList([]), + profileType: 0, //json + username: '', + walletPublicKey: 'walletPublicKey', + id: 'id', + ), ), - ), - ); + ); - await userRepository.deleteUser(); + final result = await userRepository.getOwnerOfDefaultProfile(); - verify(() => mockProfileDao.getDefaultProfile()).called(1); - verify(() => mockProfileDao.deleteProfile()).called(1); - }); - - test('should do nothing when there is no user ', () async { - when(() => mockProfileDao.deleteProfile()) - .thenAnswer((_) async => Future.value()); - when(() => mockProfileDao.getDefaultProfile()) - .thenAnswer((_) async => Future.value(null)); + expect(result, 'walletPublicKey'); + verify(() => mockProfileDao.getDefaultProfile()).called(1); + }); + + test('should return null when there is no profile', () async { + when(() => mockProfileDao.getDefaultProfile()) + .thenAnswer((_) async => Future.value(null)); - await userRepository.deleteUser(); + final result = await userRepository.getOwnerOfDefaultProfile(); - verifyNever(() => mockProfileDao.deleteProfile()); + expect(result, null); + verify(() => mockProfileDao.getDefaultProfile()).called(1); + }); }); }); } From d3974e2000ef72de6c6107fcb7236eb12aed23fe Mon Sep 17 00:00:00 2001 From: Mati Date: Tue, 10 Oct 2023 13:27:00 -0300 Subject: [PATCH 08/10] test(ardrive auth): re-structures the tests in groups PE-4752 --- test/authentication/ardrive_auth_test.dart | 1382 +++++++++----------- 1 file changed, 652 insertions(+), 730 deletions(-) diff --git a/test/authentication/ardrive_auth_test.dart b/test/authentication/ardrive_auth_test.dart index 5a80824b04..341b443ea3 100644 --- a/test/authentication/ardrive_auth_test.dart +++ b/test/authentication/ardrive_auth_test.dart @@ -50,7 +50,7 @@ void main() { secureKeyValueStore: mockSecureKeyValueStore, metadataCache: metadataCache, ); - // register call back for test drive entity + registerFallbackValue(DriveEntity( id: 'some_id', rootFolderId: 'some_id', @@ -64,505 +64,394 @@ void main() { ); }); - // test `ArDriveAuth` - group('ArDriveAuth testing isUserLoggedIn method', () { - test('Should return true when user is logged in', () async { - // arrange - when(() => mockUserRepository.hasUser()).thenAnswer((_) async => true); - // act - final isLoggedIn = await arDriveAuth.isUserLoggedIn(); - - // assert - expect(isLoggedIn, true); - }); - - test('Should return false when user is not logged in', () async { - // arrange - when(() => mockUserRepository.hasUser()).thenAnswer((_) async => false); - // act - final isLoggedIn = await arDriveAuth.isUserLoggedIn(); - - // assert - expect(isLoggedIn, false); - }); - }); - - group('ArDriveAuth testing isExistingUser method', () { - // test - test('Should return true when user is existing', () async { - // arrange - when(() => mockArweaveService.getUniqueUserDriveEntityTxs(any(), - maxRetries: any(named: 'maxRetries'))) - .thenAnswer((_) async => [TransactionCommonMixinFake()]); - // act - final isExisting = await arDriveAuth.isExistingUser(wallet); - - // assert - expect(isExisting, true); - }); - - test('Should return false when user is not existing', () async { - // arrange - when(() => mockArweaveService.getUniqueUserDriveEntityTxs(any(), - maxRetries: any(named: 'maxRetries'))).thenAnswer((_) async => []); - // act - final isExisting = await arDriveAuth.isExistingUser(wallet); - - // assert - expect(isExisting, false); - }); - }); - group('ArDriveAuth testing userHasPassword method', () { - // test - test('Should return true when user has a private drive', () async { - // arrange - when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, - maxRetries: any(named: 'maxRetries'))) - .thenAnswer((_) async => 'some_id'); - // act - final hasPassword = await arDriveAuth.userHasPassword(wallet); - - // assert - expect(hasPassword, true); - }); + group('ArDriveAuth', () { + group('isUserLoggedIn method', () { + test('Should return true when user is logged in', () async { + when(() => mockUserRepository.hasUser()).thenAnswer((_) async => true); + final isLoggedIn = await arDriveAuth.isUserLoggedIn(); - test( - 'Should return false when user does not created a password yet when they dont have any ', - () async { - // arrange - when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, - maxRetries: any(named: 'maxRetries'))).thenAnswer((_) async => null); - // act - final hasPassword = await arDriveAuth.userHasPassword(wallet); - - // assert - expect(hasPassword, false); - }); - }); + expect(isLoggedIn, true); + }); - group('testing if getFirstPrivateDriveTxId is called only once', () { - final loggedUser = User( - password: 'password', - wallet: wallet, - walletAddress: 'walletAddress', - walletBalance: BigInt.one, - cipherKey: SecretKey([]), - profileType: ProfileType.json, - ); + test('Should return false when user is not logged in', () async { + when(() => mockUserRepository.hasUser()).thenAnswer((_) async => false); + final isLoggedIn = await arDriveAuth.isUserLoggedIn(); - /// For this test we'll call the same method twice to validate if the - /// getFirstPrivateDriveTxId is called only once - - test( - 'should call getFirstPrivateDriveTxId only once when has private drives and login with sucess. ', - () async { - // arrange - when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, - maxRetries: any(named: 'maxRetries'))) - .thenAnswer((_) async => 'some_id'); - - // biometrics is not enabled - when(() => mockBiometricAuthentication.isEnabled()) - .thenAnswer((_) async => false); - - // mock cripto derive drive key - when( - () => mockArDriveCrypto.deriveDriveKey( - wallet, - any(), - any(), - ), - ).thenAnswer((invocation) => Future.value(SecretKey([]))); - - when(() => mockUserRepository.hasUser()) - .thenAnswer((invocation) => Future.value(true)); - - when(() => mockArweaveService.getLatestDriveEntityWithId( - any(), any(), any())) - .thenAnswer((invocation) => Future.value(DriveEntity( - id: 'some_id', - rootFolderId: 'some_id', - ))); - - when(() => mockUserRepository.deleteUser()) - .thenAnswer((invocation) async {}); - - when(() => - mockUserRepository.saveUser('password', ProfileType.json, wallet)) - .thenAnswer((invocation) => Future.value(null)); - - when(() => mockUserRepository.getUser('password')) - .thenAnswer((invocation) async => loggedUser); - - // act - await arDriveAuth.login(wallet, 'password', ProfileType.json); - await arDriveAuth.login(wallet, 'password', ProfileType.json); - - // assert - verify(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, - maxRetries: any(named: 'maxRetries'))).called(1); + expect(isLoggedIn, false); + }); }); - test( - 'should call getFirstPrivateDriveTxId only once when has private drives and login with sucess. ', - () async { - when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, - maxRetries: any(named: 'maxRetries'))) - .thenAnswer((_) async => 'some_id'); - // act - await arDriveAuth.userHasPassword(wallet); - await arDriveAuth.userHasPassword(wallet); - - // assert - verify(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, - maxRetries: any(named: 'maxRetries'))).called(1); - }); - }); + group('isExistingUser method', () { + test('Should return true when user is existing', () async { + when(() => mockArweaveService.getUniqueUserDriveEntityTxs(any(), + maxRetries: any(named: 'maxRetries'))) + .thenAnswer((_) async => [TransactionCommonMixinFake()]); + final isExisting = await arDriveAuth.isExistingUser(wallet); - group('ArDriveAuth testing login method without biometrics', () { - final loggedUser = User( - password: 'password', - wallet: wallet, - walletAddress: 'walletAddress', - walletBalance: BigInt.one, - cipherKey: SecretKey([]), - profileType: ProfileType.json, - ); - test( - 'should return the user when has private drives and login with sucess. ', - () async { - // arrange - when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, - maxRetries: any(named: 'maxRetries'))) - .thenAnswer((_) async => 'some_id'); - - // biometrics is not enabled - when(() => mockBiometricAuthentication.isEnabled()) - .thenAnswer((_) async => false); - - // mock cripto derive drive key - when( - () => mockArDriveCrypto.deriveDriveKey( - wallet, - any(), - any(), - ), - ).thenAnswer((invocation) => Future.value(SecretKey([]))); - - when(() => mockUserRepository.hasUser()) - .thenAnswer((invocation) => Future.value(true)); - - when(() => mockArweaveService.getLatestDriveEntityWithId( - any(), any(), any())) - .thenAnswer((invocation) => Future.value(DriveEntity( - id: 'some_id', - rootFolderId: 'some_id', - ))); - - when(() => mockUserRepository.deleteUser()) - .thenAnswer((invocation) async {}); - - when(() => - mockUserRepository.saveUser('password', ProfileType.json, wallet)) - .thenAnswer((invocation) => Future.value(null)); - - when(() => mockUserRepository.getUser('password')) - .thenAnswer((invocation) async => loggedUser); - - // act - final user = - await arDriveAuth.login(wallet, 'password', ProfileType.json); - - // assert - expect(user, isNotNull); - expect(user, isNotNull); - expect(user.password, loggedUser.password); - expect(user.wallet, loggedUser.wallet); - expect(user.walletAddress, 'walletAddress'); - expect(user.walletBalance, loggedUser.walletBalance); - expect(user.cipherKey, loggedUser.cipherKey); - expect(user.profileType, loggedUser.profileType); - }); + expect(isExisting, true); + }); - test( - 'should return the user, and save the password on secure storage when has private drives and login with sucess.', - () async { - // arrange - when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, - maxRetries: any(named: 'maxRetries'))) - .thenAnswer((_) async => 'some_id'); - - // biometrics is enabled - when(() => mockBiometricAuthentication.isEnabled()) - .thenAnswer((_) async => true); - - when(() => mockSecureKeyValueStore.putString( - 'password', - 'password', - )).thenAnswer((_) async => true); - - // mock cripto derive drive key - when( - () => mockArDriveCrypto.deriveDriveKey( - wallet, - any(), - any(), - ), - ).thenAnswer((invocation) => Future.value(SecretKey([]))); - - when(() => mockUserRepository.hasUser()) - .thenAnswer((invocation) => Future.value(true)); - - when(() => mockArweaveService.getLatestDriveEntityWithId( - any(), any(), any())) - .thenAnswer((invocation) => Future.value(DriveEntity( - id: 'some_id', - rootFolderId: 'some_id', - ))); - - when(() => mockUserRepository.deleteUser()) - .thenAnswer((invocation) async {}); - - when(() => - mockUserRepository.saveUser('password', ProfileType.json, wallet)) - .thenAnswer((invocation) => Future.value(null)); - - when(() => mockUserRepository.getUser('password')) - .thenAnswer((invocation) async => loggedUser); - - // act - final user = - await arDriveAuth.login(wallet, 'password', ProfileType.json); - - // assert - expect(user, isNotNull); - expect(user, isNotNull); - expect(user.password, loggedUser.password); - expect(user.wallet, loggedUser.wallet); - expect(user.walletAddress, 'walletAddress'); - expect(user.walletBalance, loggedUser.walletBalance); - expect(user.cipherKey, loggedUser.cipherKey); - expect(user.profileType, loggedUser.profileType); - - // calls the secure storage - verify(() => mockSecureKeyValueStore.putString('password', 'password')); - }); + test('Should return false when user is not existing', () async { + when(() => mockArweaveService.getUniqueUserDriveEntityTxs(any(), + maxRetries: any(named: 'maxRetries'))).thenAnswer((_) async => []); + final isExisting = await arDriveAuth.isExistingUser(wallet); - test('should return the user when there\'s no private drives', () async { - // arrange - // no private drives - when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, - maxRetries: any(named: 'maxRetries'))).thenAnswer((_) async => null); - - // biometrics is not enabled - when(() => mockBiometricAuthentication.isEnabled()) - .thenAnswer((_) async => false); - - when(() => mockUserRepository.hasUser()) - .thenAnswer((invocation) => Future.value(true)); - - when(() => mockUserRepository.deleteUser()) - .thenAnswer((invocation) async {}); - - when(() => - mockUserRepository.saveUser('password', ProfileType.json, wallet)) - .thenAnswer((invocation) => Future.value(null)); - - when(() => mockUserRepository.getUser('password')) - .thenAnswer((invocation) async => loggedUser); - - // act - final user = - await arDriveAuth.login(wallet, 'password', ProfileType.json); - - // assert - expect(user, isNotNull); - expect(user, isNotNull); - expect(user.password, loggedUser.password); - expect(user.wallet, loggedUser.wallet); - expect(user.walletAddress, 'walletAddress'); - expect(user.walletBalance, loggedUser.walletBalance); - expect(user.cipherKey, loggedUser.cipherKey); - expect(user.profileType, loggedUser.profileType); + expect(isExisting, false); + }); }); + group('userHasPassword method', () { + // test + test('Should return true when user has a private drive', () async { + when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, + maxRetries: any(named: 'maxRetries'))) + .thenAnswer((_) async => 'some_id'); + final hasPassword = await arDriveAuth.userHasPassword(wallet); + + expect(hasPassword, true); + }); - test( - 'should return the user, and save the password on secure storage when there\'s no private drives', - () async { - // arrange - // no private drives - when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, - maxRetries: any(named: 'maxRetries'))).thenAnswer((_) async => null); - - when(() => mockUserRepository.hasUser()) - .thenAnswer((invocation) => Future.value(true)); - - // biometrics enabled - when(() => mockBiometricAuthentication.isEnabled()) - .thenAnswer((_) async => true); - - when(() => mockSecureKeyValueStore.putString( - 'password', - 'password', - )).thenAnswer((_) async => true); - - when(() => mockUserRepository.deleteUser()) - .thenAnswer((invocation) async {}); - - when(() => - mockUserRepository.saveUser('password', ProfileType.json, wallet)) - .thenAnswer((invocation) => Future.value(null)); - - when(() => mockUserRepository.getUser('password')) - .thenAnswer((invocation) async => loggedUser); - - // act - final user = - await arDriveAuth.login(wallet, 'password', ProfileType.json); - - // assert - expect(user, isNotNull); - expect(user, isNotNull); - expect(user.password, loggedUser.password); - expect(user.wallet, loggedUser.wallet); - expect(user.walletAddress, 'walletAddress'); - expect(user.walletBalance, loggedUser.walletBalance); - expect(user.cipherKey, loggedUser.cipherKey); - expect(user.profileType, loggedUser.profileType); - - // calls the secure storage - verify(() => mockSecureKeyValueStore.putString('password', 'password')); - }); + test( + 'Should return false when user does not created a password yet when they dont have any ', + () async { + when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, + maxRetries: any(named: 'maxRetries'))) + .thenAnswer((_) async => null); + final hasPassword = await arDriveAuth.userHasPassword(wallet); - test('should return false when password is wrong', () async { - // arrange - when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, - maxRetries: any(named: 'maxRetries'))) - .thenAnswer((_) async => 'some_id'); - // mock cripto derive drive key - when( - () => mockArDriveCrypto.deriveDriveKey( - wallet, - any(), - any(), - ), - ).thenThrow(Exception('wrong password')); - - // assert - expectLater(() => arDriveAuth.login(wallet, 'password', ProfileType.json), - throwsA(isA())); + expect(hasPassword, false); + }); }); - }); + group('login method', () { + group('with biometrics', () { + final loggedUser = User( + password: 'password', + wallet: wallet, + walletAddress: 'walletAddress', + walletBalance: BigInt.one, + cipherKey: SecretKey([]), + profileType: ProfileType.json, + ); + test( + 'should return the user when has private drives and login with sucess. ', + () async { + when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, + maxRetries: any(named: 'maxRetries'))) + .thenAnswer((_) async => 'some_id'); + + when(() => mockBiometricAuthentication.isEnabled()) + .thenAnswer((_) async => false); + + when( + () => mockArDriveCrypto.deriveDriveKey( + wallet, + any(), + any(), + ), + ).thenAnswer((invocation) => Future.value(SecretKey([]))); + + when(() => mockUserRepository.hasUser()) + .thenAnswer((invocation) => Future.value(true)); + + when(() => mockArweaveService.getLatestDriveEntityWithId( + any(), any(), any())) + .thenAnswer((invocation) => Future.value(DriveEntity( + id: 'some_id', + rootFolderId: 'some_id', + ))); + + when(() => mockUserRepository.deleteUser()) + .thenAnswer((invocation) async {}); + + when(() => mockUserRepository.saveUser( + 'password', ProfileType.json, wallet)) + .thenAnswer((invocation) => Future.value(null)); + + when(() => mockUserRepository.getUser('password')) + .thenAnswer((invocation) async => loggedUser); + + final user = + await arDriveAuth.login(wallet, 'password', ProfileType.json); + + expect(user, isNotNull); + expect(user, isNotNull); + expect(user.password, loggedUser.password); + expect(user.wallet, loggedUser.wallet); + expect(user.walletAddress, 'walletAddress'); + expect(user.walletBalance, loggedUser.walletBalance); + expect(user.cipherKey, loggedUser.cipherKey); + expect(user.profileType, loggedUser.profileType); + }); + + test( + 'should return the user, and save the password on secure storage when has private drives and login with sucess.', + () async { + when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, + maxRetries: any(named: 'maxRetries'))) + .thenAnswer((_) async => 'some_id'); + + when(() => mockBiometricAuthentication.isEnabled()) + .thenAnswer((_) async => true); + + when(() => mockSecureKeyValueStore.putString( + 'password', + 'password', + )).thenAnswer((_) async => true); + + when( + () => mockArDriveCrypto.deriveDriveKey( + wallet, + any(), + any(), + ), + ).thenAnswer((invocation) => Future.value(SecretKey([]))); + + when(() => mockUserRepository.hasUser()) + .thenAnswer((invocation) => Future.value(true)); + + when(() => mockArweaveService.getLatestDriveEntityWithId( + any(), any(), any())) + .thenAnswer((invocation) => Future.value(DriveEntity( + id: 'some_id', + rootFolderId: 'some_id', + ))); + + when(() => mockUserRepository.deleteUser()) + .thenAnswer((invocation) async {}); + + when(() => mockUserRepository.saveUser( + 'password', ProfileType.json, wallet)) + .thenAnswer((invocation) => Future.value(null)); + + when(() => mockUserRepository.getUser('password')) + .thenAnswer((invocation) async => loggedUser); + + final user = + await arDriveAuth.login(wallet, 'password', ProfileType.json); + + expect(user, isNotNull); + expect(user, isNotNull); + expect(user.password, loggedUser.password); + expect(user.wallet, loggedUser.wallet); + expect(user.walletAddress, 'walletAddress'); + expect(user.walletBalance, loggedUser.walletBalance); + expect(user.cipherKey, loggedUser.cipherKey); + expect(user.profileType, loggedUser.profileType); + + verify( + () => mockSecureKeyValueStore.putString('password', 'password')); + }); + + test('should return the user when there\'s no private drives', + () async { + when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, + maxRetries: any(named: 'maxRetries'))) + .thenAnswer((_) async => null); + + when(() => mockBiometricAuthentication.isEnabled()) + .thenAnswer((_) async => false); + + when(() => mockUserRepository.hasUser()) + .thenAnswer((invocation) => Future.value(true)); + + when(() => mockUserRepository.deleteUser()) + .thenAnswer((invocation) async {}); + + when(() => mockUserRepository.saveUser( + 'password', ProfileType.json, wallet)) + .thenAnswer((invocation) => Future.value(null)); + + when(() => mockUserRepository.getUser('password')) + .thenAnswer((invocation) async => loggedUser); + + final user = + await arDriveAuth.login(wallet, 'password', ProfileType.json); + + expect(user, isNotNull); + expect(user, isNotNull); + expect(user.password, loggedUser.password); + expect(user.wallet, loggedUser.wallet); + expect(user.walletAddress, 'walletAddress'); + expect(user.walletBalance, loggedUser.walletBalance); + expect(user.cipherKey, loggedUser.cipherKey); + expect(user.profileType, loggedUser.profileType); + }); + + test( + 'should return the user, and save the password on secure storage when there\'s no private drives', + () async { + when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, + maxRetries: any(named: 'maxRetries'))) + .thenAnswer((_) async => null); + + when(() => mockUserRepository.hasUser()) + .thenAnswer((invocation) => Future.value(true)); + + when(() => mockBiometricAuthentication.isEnabled()) + .thenAnswer((_) async => true); + + when(() => mockSecureKeyValueStore.putString( + 'password', + 'password', + )).thenAnswer((_) async => true); + + when(() => mockUserRepository.deleteUser()) + .thenAnswer((invocation) async {}); + + when(() => mockUserRepository.saveUser( + 'password', ProfileType.json, wallet)) + .thenAnswer((invocation) => Future.value(null)); + + when(() => mockUserRepository.getUser('password')) + .thenAnswer((invocation) async => loggedUser); + + final user = + await arDriveAuth.login(wallet, 'password', ProfileType.json); + + expect(user, isNotNull); + expect(user, isNotNull); + expect(user.password, loggedUser.password); + expect(user.wallet, loggedUser.wallet); + expect(user.walletAddress, 'walletAddress'); + expect(user.walletBalance, loggedUser.walletBalance); + expect(user.cipherKey, loggedUser.cipherKey); + expect(user.profileType, loggedUser.profileType); + + verify( + () => mockSecureKeyValueStore.putString('password', 'password')); + }); + + test('should return false when password is wrong', () async { + when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, + maxRetries: any(named: 'maxRetries'))) + .thenAnswer((_) async => 'some_id'); + when( + () => mockArDriveCrypto.deriveDriveKey( + wallet, + any(), + any(), + ), + ).thenThrow(Exception('wrong password')); + + expectLater( + () => arDriveAuth.login(wallet, 'password', ProfileType.json), + throwsA(isA())); + }); + }); - group('testing ArDriveAuth unlockUser method', () { - final unlockedUser = User( - password: 'password', - wallet: wallet, - walletAddress: 'walletAddress', - walletBalance: BigInt.one, - cipherKey: SecretKey([]), - profileType: ProfileType.json, - ); + group('without biometrics', () { + const localizedReason = 'Please authenticate with biometrics'; - test('should return the user when password is correct', () async { - // arrange - when(() => mockUserRepository.getUser('password')) - .thenAnswer((invocation) async => unlockedUser); - - // act - final user = await arDriveAuth.unlockUser(password: 'password'); - - // assert - // compare user - expect(user, isNotNull); - expect(arDriveAuth.currentUser, isNotNull); - expect(user.password, unlockedUser.password); - expect(user.wallet, unlockedUser.wallet); - expect(user.walletAddress, 'walletAddress'); - expect(user.walletBalance, unlockedUser.walletBalance); - expect(user.cipherKey, unlockedUser.cipherKey); - expect(user.profileType, unlockedUser.profileType); - }); + final loggedUser = User( + password: 'password123', + wallet: wallet, + walletAddress: 'walletAddress', + walletBalance: BigInt.one, + cipherKey: SecretKey([]), + profileType: ProfileType.json, + ); - test('should throw when password is not correct', () async { - // arrange - when(() => mockUserRepository.getUser('password')) - .thenThrow(Exception('wrong password')); + test( + 'should unlock user when biometric authentication succeeds and user is logged in', + () async { + when(() => mockBiometricAuthentication.authenticate( + localizedReason: localizedReason, + useCached: true, + )).thenAnswer((_) async => true); - expectLater(() => arDriveAuth.unlockUser(password: 'password'), - throwsA(isA())); - }); - }); - - group('testing if getFirstPrivateDriveTxId is called only once', () {}); + when(() => mockSecureKeyValueStore.getString('password')) + .thenAnswer((_) async => 'password123'); - group('testing ArDriveAuth logout method', () { - final unlockedUser = User( - password: 'password', - wallet: wallet, - walletAddress: 'walletAddress', - walletBalance: BigInt.one, - cipherKey: SecretKey([]), - profileType: ProfileType.json, - ); + when(() => mockUserRepository.hasUser()) + .thenAnswer((_) async => true); - test('should delete the current user and delete it when user is logged in', - () async { - when(() => mockUserRepository.hasUser()) - .thenAnswer((invocation) => Future.value(true)); - when(() => mockUserRepository.getUser('password')) - .thenAnswer((invocation) async => unlockedUser); - - // act - await arDriveAuth.unlockUser(password: 'password'); - - // arrange - when(() => mockUserRepository.deleteUser()) - .thenAnswer((invocation) async {}); - when(() => mockSecureKeyValueStore.remove('password')) - .thenAnswer((invocation) => Future.value(true)); - when(() => mockSecureKeyValueStore.remove('biometricEnabled')) - .thenAnswer((invocation) => Future.value(true)); - when(() => mockDatabaseHelpers.deleteAllTables()) - .thenAnswer((invocation) async {}); - - // act - await arDriveAuth.logout(); - - // assert - expect(() => arDriveAuth.currentUser, - throwsA(isA())); - expect(arDriveAuth.firstPrivateDriveTxId, isNull); - verify(() => mockSecureKeyValueStore.remove('password')).called(1); - verify(() => mockSecureKeyValueStore.remove('biometricEnabled')) - .called(1); - verify(() => mockDatabaseHelpers.deleteAllTables()).called(1); - }); + when(() => mockUserRepository.getUser('password123')) + .thenAnswer((invocation) => Future.value(loggedUser)); - /// This is for the case when has user is true but the user is not logged in - /// one example is the forget wallet page before the user is logged in - test('should delete the current user and delete it when user is not logged', - () async { - when(() => mockUserRepository.hasUser()) - .thenAnswer((invocation) => Future.value(false)); - - // arrange - when(() => mockDatabaseHelpers.deleteAllTables()) - .thenAnswer((invocation) async {}); - - // act - await arDriveAuth.logout(); - - // assert - verifyNever(() => mockSecureKeyValueStore.remove('password')); - verifyNever(() => mockSecureKeyValueStore.remove('biometricEnabled')); - verify(() => mockDatabaseHelpers.deleteAllTables()).called(1); - expect(() => arDriveAuth.currentUser, - throwsA(isA())); + final result = await arDriveAuth.unlockWithBiometrics( + localizedReason: localizedReason, + ); + + expect(result, isA()); + + verify(() => mockBiometricAuthentication.authenticate( + localizedReason: localizedReason, + useCached: true, + )); + + verify(() => mockSecureKeyValueStore.getString('password')); + verify(() => mockUserRepository.hasUser()); + }); + + test( + 'should throw AuthenticationFailedException when biometric authentication succeeds but user is not logged in', + () async { + when(() => mockBiometricAuthentication.authenticate( + localizedReason: localizedReason, + useCached: true, + )).thenAnswer((_) async => true); + + when(() => mockUserRepository.hasUser()) + .thenAnswer((_) async => false); + + expect( + arDriveAuth.unlockWithBiometrics(localizedReason: localizedReason), + throwsA(isA()), + ); + + verifyNever(() => mockBiometricAuthentication.authenticate( + localizedReason: localizedReason, + useCached: true, + )); + + verify(() => mockUserRepository.hasUser()); + verifyNever(() => mockSecureKeyValueStore.getString('password')); + }); + + test( + 'should throw AuthenticationFailedException when biometric authentication fails due to user not authenticating', + () async { + when(() => mockBiometricAuthentication.authenticate( + localizedReason: localizedReason, + useCached: true, + )).thenAnswer((_) async => false); + + when(() => mockUserRepository.hasUser()) + .thenAnswer((_) async => true); + + expectLater( + arDriveAuth.unlockWithBiometrics(localizedReason: localizedReason), + throwsA(isA()), + ); + + verifyNever(() => mockSecureKeyValueStore.getString('password')); + }); + + // Biometric authentication fails due to password not found, + // should throw AuthenticationFailedException + test( + 'should throw AuthenticationUnknownException when biometric authentication fails due to password not found', + () async { + when(() => mockBiometricAuthentication.authenticate( + localizedReason: localizedReason, + useCached: true, + )).thenAnswer((_) async => true); + + when(() => mockUserRepository.hasUser()) + .thenAnswer((_) async => true); + + when(() => mockSecureKeyValueStore.getString('password')) + .thenAnswer((_) async => null); + + expectLater( + arDriveAuth.unlockWithBiometrics(localizedReason: localizedReason), + throwsA(isA()), + ); + }); + }); }); - test('testing login + logout', () async { - final loggedUser = User( + group('unlockUser method', () { + final unlockedUser = User( password: 'password', wallet: wallet, walletAddress: 'walletAddress', @@ -570,118 +459,33 @@ void main() { cipherKey: SecretKey([]), profileType: ProfileType.json, ); - // arrange login - when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, - maxRetries: any(named: 'maxRetries'))) - .thenAnswer((_) async => 'some_id'); - - // biometrics is not enabled - when(() => mockBiometricAuthentication.isEnabled()) - .thenAnswer((_) async => false); - - // mock cripto derive drive key - when( - () => mockArDriveCrypto.deriveDriveKey( - wallet, - any(), - any(), - ), - ).thenAnswer((invocation) => Future.value(SecretKey([]))); - - when(() => mockUserRepository.hasUser()) - .thenAnswer((invocation) => Future.value(true)); - - when(() => mockArweaveService.getLatestDriveEntityWithId( - any(), any(), any())) - .thenAnswer((invocation) => Future.value(DriveEntity( - id: 'some_id', - rootFolderId: 'some_id', - ))); - - when(() => mockUserRepository.deleteUser()) - .thenAnswer((invocation) async {}); - - when(() => - mockUserRepository.saveUser('password', ProfileType.json, wallet)) - .thenAnswer((invocation) => Future.value(null)); - - when(() => mockUserRepository.getUser('password')) - .thenAnswer((invocation) async => loggedUser); - - /// arrange logout - when(() => mockUserRepository.deleteUser()) - .thenAnswer((invocation) async {}); - when(() => mockSecureKeyValueStore.remove('password')) - .thenAnswer((invocation) => Future.value(true)); - when(() => mockSecureKeyValueStore.remove('biometricEnabled')) - .thenAnswer((invocation) => Future.value(true)); - when(() => mockDatabaseHelpers.deleteAllTables()) - .thenAnswer((invocation) async {}); - - await arDriveAuth.login(wallet, 'password', ProfileType.json); - - await arDriveAuth.logout(); - - /// verifies that we cleaned up the user - expect(arDriveAuth.firstPrivateDriveTxId, isNull); - expect(() => arDriveAuth.currentUser, - throwsA(isA())); - verify(() => mockSecureKeyValueStore.remove('password')).called(1); - verify(() => mockSecureKeyValueStore.remove('biometricEnabled')) - .called(1); - verify(() => mockDatabaseHelpers.deleteAllTables()).called(1); - verify(() => mockUserRepository.deleteUser()).called(1); - }); - }); - group('testing ArDriveAuth onAuthStateChanged method', () { - late User loggedUser; + test('should return the user when password is correct', () async { + when(() => mockUserRepository.getUser('password')) + .thenAnswer((invocation) async => unlockedUser); - setUp(() { - loggedUser = User( - password: 'password', - wallet: wallet, - walletAddress: 'walletAddress', - walletBalance: BigInt.one, - cipherKey: SecretKey([]), - profileType: ProfileType.json, - ); - // arrange - when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, - maxRetries: any(named: 'maxRetries'))) - .thenAnswer((_) async => 'some_id'); - // mock crypto derive drive key - when( - () => mockArDriveCrypto.deriveDriveKey( - wallet, - any(), - any(), - ), - ).thenAnswer((invocation) => Future.value(SecretKey([]))); - when(() => mockUserRepository.hasUser()) - .thenAnswer((invocation) => Future.value(true)); - when(() => mockArweaveService.getLatestDriveEntityWithId( - any(), any(), any())) - .thenAnswer((invocation) => Future.value(DriveEntity( - id: 'some_id', - rootFolderId: 'some_id', - ))); - when(() => mockUserRepository.deleteUser()) - .thenAnswer((invocation) async {}); - when(() => - mockUserRepository.saveUser('password', ProfileType.json, wallet)) - .thenAnswer((invocation) => Future.value(null)); - - when(() => mockUserRepository.getUser('password')) - .thenAnswer((invocation) async => loggedUser); - }); - test('should change the state when user logs in', () async { - // arrange - // biometrics disabled - when(() => mockBiometricAuthentication.isEnabled()) - .thenAnswer((_) async => false); + final user = await arDriveAuth.unlockUser(password: 'password'); - final loggedUser = User( + expect(user, isNotNull); + expect(arDriveAuth.currentUser, isNotNull); + expect(user.password, unlockedUser.password); + expect(user.wallet, unlockedUser.wallet); + expect(user.walletAddress, 'walletAddress'); + expect(user.walletBalance, unlockedUser.walletBalance); + expect(user.cipherKey, unlockedUser.cipherKey); + expect(user.profileType, unlockedUser.profileType); + }); + + test('should throw when password is not correct', () async { + when(() => mockUserRepository.getUser('password')) + .thenThrow(Exception('wrong password')); + + expectLater(() => arDriveAuth.unlockUser(password: 'password'), + throwsA(isA())); + }); + }); + group('logout method', () { + final unlockedUser = User( password: 'password', wallet: wallet, walletAddress: 'walletAddress', @@ -690,166 +494,284 @@ void main() { profileType: ProfileType.json, ); - // act - arDriveAuth.onAuthStateChanged().listen((user) { - // assert - expect(user, isNotNull); - expect(user!.password, loggedUser.password); - expect(user.wallet, loggedUser.wallet); - expect(user.walletAddress, 'walletAddress'); - expect(user.walletBalance, loggedUser.walletBalance); - expect(user.cipherKey, loggedUser.cipherKey); - expect(user.profileType, loggedUser.profileType); - }); - - await arDriveAuth.login(wallet, 'password', ProfileType.json); - }); - - test('should change the state when user logs out', () async { - when(() => mockUserRepository.hasUser()) - .thenAnswer((invocation) => Future.value(true)); - when(() => mockUserRepository.getUser('password')) - .thenAnswer((invocation) async => loggedUser); - - // act - await arDriveAuth.unlockUser(password: 'password'); - - // arrange - when(() => mockUserRepository.deleteUser()) - .thenAnswer((invocation) async {}); - when(() => mockSecureKeyValueStore.remove('password')) - .thenAnswer((invocation) => Future.value(true)); - when(() => mockSecureKeyValueStore.remove('biometricEnabled')) - .thenAnswer((invocation) => Future.value(true)); - when(() => mockDatabaseHelpers.deleteAllTables()) - .thenAnswer((invocation) async {}); - - // act - arDriveAuth.onAuthStateChanged().listen((user) { - // assert - expect(user, isNull); + test( + 'should delete the current user and delete it when user is logged in', + () async { + when(() => mockUserRepository.hasUser()) + .thenAnswer((invocation) => Future.value(true)); + when(() => mockUserRepository.getUser('password')) + .thenAnswer((invocation) async => unlockedUser); + + await arDriveAuth.unlockUser(password: 'password'); + + when(() => mockUserRepository.deleteUser()) + .thenAnswer((invocation) async {}); + when(() => mockSecureKeyValueStore.remove('password')) + .thenAnswer((invocation) => Future.value(true)); + when(() => mockSecureKeyValueStore.remove('biometricEnabled')) + .thenAnswer((invocation) => Future.value(true)); + when(() => mockDatabaseHelpers.deleteAllTables()) + .thenAnswer((invocation) async {}); + + await arDriveAuth.logout(); + + expect(() => arDriveAuth.currentUser, + throwsA(isA())); + expect(arDriveAuth.firstPrivateDriveTxId, isNull); + verify(() => mockSecureKeyValueStore.remove('password')).called(1); + verify(() => mockSecureKeyValueStore.remove('biometricEnabled')) + .called(1); + verify(() => mockDatabaseHelpers.deleteAllTables()).called(1); }); - await arDriveAuth.logout(); - }); - }); - - group('unlockWithBiometrics', () { - const localizedReason = 'Please authenticate with biometrics'; + /// This is for the case when has user is true but the user is not logged in + /// one example is the forget wallet page before the user is logged in + test( + 'should delete the current user and delete it when user is not logged', + () async { + when(() => mockUserRepository.hasUser()) + .thenAnswer((invocation) => Future.value(false)); - final loggedUser = User( - password: 'password123', - wallet: wallet, - walletAddress: 'walletAddress', - walletBalance: BigInt.one, - cipherKey: SecretKey([]), - profileType: ProfileType.json, - ); - - test( - 'should unlock user when biometric authentication succeeds and user is logged in', - () async { - // Mock dependencies - when(() => mockBiometricAuthentication.authenticate( - localizedReason: localizedReason, - useCached: true, - )).thenAnswer((_) async => true); + when(() => mockDatabaseHelpers.deleteAllTables()) + .thenAnswer((invocation) async {}); - when(() => mockSecureKeyValueStore.getString('password')) - .thenAnswer((_) async => 'password123'); + await arDriveAuth.logout(); - when(() => mockUserRepository.hasUser()).thenAnswer((_) async => true); - - when(() => mockUserRepository.getUser('password123')) - .thenAnswer((invocation) => Future.value(loggedUser)); - - // Invoke the method under test - final result = await arDriveAuth.unlockWithBiometrics( - localizedReason: localizedReason, - ); - - // Verify the result - expect(result, isA()); - - // Verify method calls on dependencies - verify(() => mockBiometricAuthentication.authenticate( - localizedReason: localizedReason, - useCached: true, - )); + verifyNever(() => mockSecureKeyValueStore.remove('password')); + verifyNever(() => mockSecureKeyValueStore.remove('biometricEnabled')); + verify(() => mockDatabaseHelpers.deleteAllTables()).called(1); + expect(() => arDriveAuth.currentUser, + throwsA(isA())); + }); - verify(() => mockSecureKeyValueStore.getString('password')); - verify(() => mockUserRepository.hasUser()); + test('testing login + logout', () async { + final loggedUser = User( + password: 'password', + wallet: wallet, + walletAddress: 'walletAddress', + walletBalance: BigInt.one, + cipherKey: SecretKey([]), + profileType: ProfileType.json, + ); + when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, + maxRetries: any(named: 'maxRetries'))) + .thenAnswer((_) async => 'some_id'); + + when(() => mockBiometricAuthentication.isEnabled()) + .thenAnswer((_) async => false); + + when( + () => mockArDriveCrypto.deriveDriveKey( + wallet, + any(), + any(), + ), + ).thenAnswer((invocation) => Future.value(SecretKey([]))); + + when(() => mockUserRepository.hasUser()) + .thenAnswer((invocation) => Future.value(true)); + + when(() => mockArweaveService.getLatestDriveEntityWithId( + any(), any(), any())) + .thenAnswer((invocation) => Future.value(DriveEntity( + id: 'some_id', + rootFolderId: 'some_id', + ))); + + when(() => mockUserRepository.deleteUser()) + .thenAnswer((invocation) async {}); + + when(() => mockUserRepository.saveUser( + 'password', ProfileType.json, wallet)) + .thenAnswer((invocation) => Future.value(null)); + + when(() => mockUserRepository.getUser('password')) + .thenAnswer((invocation) async => loggedUser); + + when(() => mockUserRepository.deleteUser()) + .thenAnswer((invocation) async {}); + when(() => mockSecureKeyValueStore.remove('password')) + .thenAnswer((invocation) => Future.value(true)); + when(() => mockSecureKeyValueStore.remove('biometricEnabled')) + .thenAnswer((invocation) => Future.value(true)); + when(() => mockDatabaseHelpers.deleteAllTables()) + .thenAnswer((invocation) async {}); + + await arDriveAuth.login(wallet, 'password', ProfileType.json); + + await arDriveAuth.logout(); + + expect(arDriveAuth.firstPrivateDriveTxId, isNull); + expect(() => arDriveAuth.currentUser, + throwsA(isA())); + verify(() => mockSecureKeyValueStore.remove('password')).called(1); + verify(() => mockSecureKeyValueStore.remove('biometricEnabled')) + .called(1); + verify(() => mockDatabaseHelpers.deleteAllTables()).called(1); + verify(() => mockUserRepository.deleteUser()).called(1); + }); }); + group('onAuthStateChanged method', () { + late User loggedUser; + + setUp(() { + loggedUser = User( + password: 'password', + wallet: wallet, + walletAddress: 'walletAddress', + walletBalance: BigInt.one, + cipherKey: SecretKey([]), + profileType: ProfileType.json, + ); + when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, + maxRetries: any(named: 'maxRetries'))) + .thenAnswer((_) async => 'some_id'); + when( + () => mockArDriveCrypto.deriveDriveKey( + wallet, + any(), + any(), + ), + ).thenAnswer((invocation) => Future.value(SecretKey([]))); + when(() => mockUserRepository.hasUser()) + .thenAnswer((invocation) => Future.value(true)); + when(() => mockArweaveService.getLatestDriveEntityWithId( + any(), any(), any())) + .thenAnswer((invocation) => Future.value(DriveEntity( + id: 'some_id', + rootFolderId: 'some_id', + ))); + when(() => mockUserRepository.deleteUser()) + .thenAnswer((invocation) async {}); + when(() => mockUserRepository.saveUser( + 'password', ProfileType.json, wallet)) + .thenAnswer((invocation) => Future.value(null)); + + when(() => mockUserRepository.getUser('password')) + .thenAnswer((invocation) async => loggedUser); + }); + test('should change the state when user logs in', () async { + when(() => mockBiometricAuthentication.isEnabled()) + .thenAnswer((_) async => false); + + final loggedUser = User( + password: 'password', + wallet: wallet, + walletAddress: 'walletAddress', + walletBalance: BigInt.one, + cipherKey: SecretKey([]), + profileType: ProfileType.json, + ); + + arDriveAuth.onAuthStateChanged().listen((user) { + expect(user, isNotNull); + expect(user!.password, loggedUser.password); + expect(user.wallet, loggedUser.wallet); + expect(user.walletAddress, 'walletAddress'); + expect(user.walletBalance, loggedUser.walletBalance); + expect(user.cipherKey, loggedUser.cipherKey); + expect(user.profileType, loggedUser.profileType); + }); + + await arDriveAuth.login(wallet, 'password', ProfileType.json); + }); - test( - 'should throw AuthenticationFailedException when biometric authentication succeeds but user is not logged in', - () async { - // Mock dependencies - when(() => mockBiometricAuthentication.authenticate( - localizedReason: localizedReason, - useCached: true, - )).thenAnswer((_) async => true); + test('should change the state when user logs out', () async { + when(() => mockUserRepository.hasUser()) + .thenAnswer((invocation) => Future.value(true)); + when(() => mockUserRepository.getUser('password')) + .thenAnswer((invocation) async => loggedUser); - when(() => mockUserRepository.hasUser()).thenAnswer((_) async => false); + await arDriveAuth.unlockUser(password: 'password'); - // Invoke the method under test - expect( - arDriveAuth.unlockWithBiometrics(localizedReason: localizedReason), - throwsA(isA()), - ); + when(() => mockUserRepository.deleteUser()) + .thenAnswer((invocation) async {}); + when(() => mockSecureKeyValueStore.remove('password')) + .thenAnswer((invocation) => Future.value(true)); + when(() => mockSecureKeyValueStore.remove('biometricEnabled')) + .thenAnswer((invocation) => Future.value(true)); + when(() => mockDatabaseHelpers.deleteAllTables()) + .thenAnswer((invocation) async {}); - // Verify method calls on dependencies - verifyNever(() => mockBiometricAuthentication.authenticate( - localizedReason: localizedReason, - useCached: true, - )); + arDriveAuth.onAuthStateChanged().listen((user) { + expect(user, isNull); + }); - verify(() => mockUserRepository.hasUser()); - verifyNever(() => mockSecureKeyValueStore.getString('password')); + await arDriveAuth.logout(); + }); }); - - // should throw AuthenticationFailedException when biometric authentication fails due to user not authenticating' - test( - 'should throw AuthenticationFailedException when biometric authentication fails due to user not authenticating', - () async { - // Mock dependencies - when(() => mockBiometricAuthentication.authenticate( - localizedReason: localizedReason, - useCached: true, - )).thenAnswer((_) async => false); - - when(() => mockUserRepository.hasUser()).thenAnswer((_) async => true); - - // Invoke the method under test - expectLater( - arDriveAuth.unlockWithBiometrics(localizedReason: localizedReason), - throwsA(isA()), + group('getFirstPrivateDriveTxId method', () { + final loggedUser = User( + password: 'password', + wallet: wallet, + walletAddress: 'walletAddress', + walletBalance: BigInt.one, + cipherKey: SecretKey([]), + profileType: ProfileType.json, ); - verifyNever(() => mockSecureKeyValueStore.getString('password')); - }); - - // Biometric authentication fails due to password not found, - // should throw AuthenticationFailedException - test( - 'should throw AuthenticationUnknownException when biometric authentication fails due to password not found', - () async { - // Mock dependencies - when(() => mockBiometricAuthentication.authenticate( - localizedReason: localizedReason, - useCached: true, - )).thenAnswer((_) async => true); + /// For this test we'll call the same method twice to validate if the + /// getFirstPrivateDriveTxId is called only once + + test( + 'should call getFirstPrivateDriveTxId only once when has private drives and login with sucess. ', + () async { + // arrange + when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, + maxRetries: any(named: 'maxRetries'))) + .thenAnswer((_) async => 'some_id'); + + when(() => mockBiometricAuthentication.isEnabled()) + .thenAnswer((_) async => false); + + when( + () => mockArDriveCrypto.deriveDriveKey( + wallet, + any(), + any(), + ), + ).thenAnswer((invocation) => Future.value(SecretKey([]))); + + when(() => mockUserRepository.hasUser()) + .thenAnswer((invocation) => Future.value(true)); + + when(() => mockArweaveService.getLatestDriveEntityWithId( + any(), any(), any())) + .thenAnswer((invocation) => Future.value(DriveEntity( + id: 'some_id', + rootFolderId: 'some_id', + ))); + + when(() => mockUserRepository.deleteUser()) + .thenAnswer((invocation) async {}); + + when(() => mockUserRepository.saveUser( + 'password', ProfileType.json, wallet)) + .thenAnswer((invocation) => Future.value(null)); + + when(() => mockUserRepository.getUser('password')) + .thenAnswer((invocation) async => loggedUser); + + await arDriveAuth.login(wallet, 'password', ProfileType.json); + await arDriveAuth.login(wallet, 'password', ProfileType.json); + + verify(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, + maxRetries: any(named: 'maxRetries'))).called(1); + }); - when(() => mockUserRepository.hasUser()).thenAnswer((_) async => true); + test( + 'should call getFirstPrivateDriveTxId only once when has private drives and login with sucess. ', + () async { + when(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, + maxRetries: any(named: 'maxRetries'))) + .thenAnswer((_) async => 'some_id'); - when(() => mockSecureKeyValueStore.getString('password')) - .thenAnswer((_) async => null); + await arDriveAuth.userHasPassword(wallet); + await arDriveAuth.userHasPassword(wallet); - // Invoke the method under test - expectLater( - arDriveAuth.unlockWithBiometrics(localizedReason: localizedReason), - throwsA(isA()), - ); + verify(() => mockArweaveService.getFirstPrivateDriveTxId(wallet, + maxRetries: any(named: 'maxRetries'))).called(1); + }); }); }); } From 4474ed83c5a0a3244b6df7dd685b1b491801af20 Mon Sep 17 00:00:00 2001 From: Mati Date: Tue, 10 Oct 2023 13:40:12 -0300 Subject: [PATCH 09/10] test(ardrive auth): adds unit tests for getOwnerOfDefaultProfile PE-4752 --- test/authentication/ardrive_auth_test.dart | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/authentication/ardrive_auth_test.dart b/test/authentication/ardrive_auth_test.dart index 341b443ea3..dc9d87b019 100644 --- a/test/authentication/ardrive_auth_test.dart +++ b/test/authentication/ardrive_auth_test.dart @@ -774,4 +774,29 @@ void main() { }); }); }); + + group('getWalletAddress method', () { + test('should return the wallet address when user is logged in', () async { + const publicKey = + 'y6tP8PVR5VSOsouFIDFBIDAAQ19b25pQRcDrdYDyBr7dtW5sKHHpcrA-I1scOk5H_ZX22_4E5T568SToox_y5XeBJ3nw9kB8HzgdmQyMnEBnb050NvKv2w47vD7I0I7qrRSqJ8dt3Q3UPZvkys9sm2HEpoMaaJ-Fx44ww1CYs5U2KXI-BSpwA7SQE3eRIESZ-kD4D9TYt5ykuslRKOM1lZSiRxGqKfpnutKNZ5tdl5-d9Z4eZ2qeMETevbhXUjh8p7sJbWb02hHozNJUBawuZ3xQ2KRQqymFM9GqKE8EnHIVvR2V1LIkbcWbEIuSpqviwLschZpQ9pbTljMOqKR7_ox_199qyU9z4nnJsGLBZnv5ilGs1J5dlCitDlRCMJ53A9e5GojEzKOpzaFfHlei9DD2MUN8cKc7_pQuFuhNwkMwzKduekmFgRdvIr0ZlyRiG02CX3txpXjqw5iBYjhs4fQhNE0nj9FzBnEm4z_NltyTAf8W6TbKN40AFn__A5-wUDQ1XdA7bgPfz4UMDyldkHLXTzdgn5jg2-233IO5PK0xOes0jMRdR1d0jqF38wldgWtBt6oDk8jic6hCUCP29zoYqlNRcJHKFRDWZaZMkmVQON6-EvilC7-sGiKsbcTIhRw-wuC0guQFHSUiJpJZB9hWmMHejGqME0mCin6gQFM'; + const expectedWalletAddress = + 'e3a1dzQ1DlGBHa7hjOzTtODLLHwwRsbq0evWvJMqkkc'; + + when(() => mockUserRepository.getOwnerOfDefaultProfile()) + .thenAnswer((_) async => publicKey); + + final walletAddress = await arDriveAuth.getWalletAddress(); + + expect(walletAddress, expectedWalletAddress); + }); + + test('should return null when user is not logged in', () async { + when(() => mockUserRepository.getOwnerOfDefaultProfile()) + .thenAnswer((_) async => null); + + final walletAddress = await arDriveAuth.getWalletAddress(); + + expect(walletAddress, null); + }); + }); } From eac57f9894dc07360ae9a4518ce98fa85fbc4f9c Mon Sep 17 00:00:00 2001 From: Mati Date: Wed, 11 Oct 2023 14:38:57 -0300 Subject: [PATCH 10/10] feat(login page): make wallet address be displayed when the user loaded the JSON wallet, and the public key is not present in the db PE-4786 --- .../login/views/login_page.dart | 68 ++++++++++++------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/lib/authentication/login/views/login_page.dart b/lib/authentication/login/views/login_page.dart index 5d07b76948..e02de25fd8 100644 --- a/lib/authentication/login/views/login_page.dart +++ b/lib/authentication/login/views/login_page.dart @@ -689,32 +689,9 @@ class _PromptPasswordViewState extends State { textAlign: TextAlign.center, style: ArDriveTypography.headline.headline4Bold(), ), - FutureBuilder( - future: context.read().getWalletAddress(), - builder: - (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - appLocalizationsOf(context).walletAddress, - style: ArDriveTypography.body - .captionRegular() - .copyWith(fontSize: 18), - ), - const SizedBox(width: 8), - TruncatedAddress( - walletAddress: snapshot.data!, - fontSize: 18, - ), - ], - ); - } else { - return const SizedBox.shrink(); - } - }, + _buildAddressPreview( + context, + maybeWallet: widget.wallet, ), Column( children: [ @@ -2669,3 +2646,42 @@ class _LoginCopyButtonState extends State { } } } + +Widget _buildAddressPreview( + BuildContext context, { + required Wallet? maybeWallet, +}) { + Future getWalletAddress() async { + if (maybeWallet == null) { + return context.read().getWalletAddress(); + } + return maybeWallet.getAddress(); + } + + return FutureBuilder( + future: getWalletAddress(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + appLocalizationsOf(context).walletAddress, + style: ArDriveTypography.body + .captionRegular() + .copyWith(fontSize: 18), + ), + const SizedBox(width: 8), + TruncatedAddress( + walletAddress: snapshot.data!, + fontSize: 18, + ), + ], + ); + } else { + return const SizedBox.shrink(); + } + }, + ); +}