From 19bb535fda9be46ab9a7a9182261ebf7488990ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Tro=C3=ABng?= Date: Wed, 6 Jul 2022 22:50:38 +0200 Subject: [PATCH] added all tests relating to OATH options, and a first TOTP test (max accounts) --- integration_test/oath_test.dart | 194 ++++++++++++++++++-------------- integration_test/reset_key.dart | 16 +-- lib/oath/views/oath_screen.dart | 23 ++-- 3 files changed, 123 insertions(+), 110 deletions(-) diff --git a/integration_test/oath_test.dart b/integration_test/oath_test.dart index 7ef6b48f..a450e54d 100644 --- a/integration_test/oath_test.dart +++ b/integration_test/oath_test.dart @@ -4,7 +4,6 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; -import 'package:yubico_authenticator/app/views/no_device_screen.dart'; import 'package:yubico_authenticator/oath/views/account_list.dart'; import 'package:yubico_authenticator/oath/views/oath_screen.dart'; @@ -24,11 +23,11 @@ String randomPadded() { } String generateRandomIssuer() { - return 'i' + randomPadded(); + return 'i${randomPadded()}'; } String generateRandomName() { - return 'n' + randomPadded(); + return 'n${randomPadded()}'; } String generateRandomSecret() { @@ -40,111 +39,141 @@ void main() { final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; - group('OATH tests', () { - /// For these tests there are defined Keys in manage_password_dialog.dart - testWidgets('set password', (WidgetTester tester) async { + group('OATH Options', () { + /* + These tests verify that all oath options are verified to function correctly by: + 1. setting firsPassword and verifying it + 2. logging in and changing to secondPassword and verifying it + 3. changing to thirdPassword + 4. removing thirdPassword + */ + testWidgets('OATH: set firstPassword', (WidgetTester tester) async { await tester.pumpWidget(await getAuthenticatorApp()); await tester.pump(const Duration(milliseconds: 500)); - expect(find.byType(NoDeviceScreen), findsNothing, - reason: 'No YubiKey connected'); - expect(find.byType(OathScreen), findsOneWidget); + var firstPassword = 'aaa111'; - await tester.tap(find.byType(FloatingActionButton)); - await tester.pump(const Duration(milliseconds: 300)); + /// expect(find.byType(OathScreen), findsOneWidget); <<< I am not certain if this is needed. + + await tester.tap(find.byIcon(Icons.tune)); + await tester.pump(const Duration(milliseconds: 100)); await tester.tap(find.text('Set password')); - await tester.pump(const Duration(milliseconds: 300)); + await tester.pump(const Duration(milliseconds: 100)); - var first_password = 'aaa111'; - - /// TODO: I don't understand why these Keys don't work as intended - await tester.enterText( - find.byKey(const Key('new oath password')), first_password); - await tester.enterText( - find.byKey(const Key('confirm oath password')), first_password); + await tester.enterText(find.byKey(const Key('new oath password')), firstPassword); + await tester.pump(); + await tester.enterText(find.byKey(const Key('confirm oath password')), firstPassword); await tester.pump(); await tester.tap(find.text('Save')); - await tester.pump(const Duration(milliseconds: 300)); + await tester.pump(const Duration(milliseconds: 100)); - /// TODO: verification of state here: restarting app and entering password - await tester.pump(const Duration(seconds: 3)); + await tester.pump(const Duration(milliseconds: 1000)); }); - testWidgets('change password', (WidgetTester tester) async { + testWidgets('OATH: verify firstPassword', (WidgetTester tester) async { await tester.pumpWidget(await getAuthenticatorApp()); await tester.pump(const Duration(milliseconds: 500)); - expect(find.byType(NoDeviceScreen), findsNothing, - reason: 'No YubiKey connected'); - expect(find.byType(OathScreen), findsOneWidget); + var firstPassword = 'aaa111'; - await tester.tap(find.byType(FloatingActionButton)); - await tester.pump(const Duration(milliseconds: 300)); + await tester.enterText(find.byKey(const Key('oath password')), firstPassword); + await tester.pump(); + + /// TODO: verification of state here: see that list of accounts is shown + await tester.pump(const Duration(milliseconds: 1000)); + }); + testWidgets('OATH: set secondPassword', (WidgetTester tester) async { + await tester.pumpWidget(await getAuthenticatorApp()); + await tester.pump(const Duration(milliseconds: 500)); + + var firstPassword = 'aaa111'; + var secondPassword = 'bbb222'; + + await tester.enterText(find.byKey(const Key('oath password')), firstPassword); + await tester.pump(); + await tester.tap(find.byKey(const Key('oath unlock'))); + + await tester.tap(find.byIcon(Icons.tune)); + await tester.pump(const Duration(milliseconds: 100)); await tester.tap(find.text('Manage password')); - await tester.pump(const Duration(milliseconds: 300)); + await tester.pump(const Duration(milliseconds: 100)); - var current_password = 'aaa111'; - var second_password = 'bbb222'; + await tester.enterText(find.byKey(const Key('current oath password')), firstPassword); + await tester.pump(); + await tester.enterText(find.byKey(const Key('new oath password')), secondPassword); - /// TODO: I don't understand why these Keys don't work as intended - await tester.enterText( - find.byKey(const Key('current oath password')), current_password); - await tester.enterText( - find.byKey(const Key('new oath password')), second_password); - await tester.enterText( - find.byKey(const Key('confirm oath password')), second_password); + await tester.pump(); + await tester.enterText(find.byKey(const Key('confirm oath password')), secondPassword); await tester.pump(); await tester.tap(find.text('Save')); - await tester.pump(const Duration(milliseconds: 300)); + await tester.pump(const Duration(milliseconds: 100)); - /// TODO: verification of state here: restarting app and entering password - await tester.pump(const Duration(seconds: 3)); + await tester.pump(const Duration(milliseconds: 1000)); }); - testWidgets('remove password', (WidgetTester tester) async { + testWidgets('OATH: set thirdPassword', (WidgetTester tester) async { await tester.pumpWidget(await getAuthenticatorApp()); await tester.pump(const Duration(milliseconds: 500)); - expect(find.byType(NoDeviceScreen), findsNothing, - reason: 'No YubiKey connected'); - expect(find.byType(OathScreen), findsOneWidget); + var secondPassword = 'bbb222'; + var thirdPassword = 'ccc333'; - await tester.tap(find.byType(FloatingActionButton)); - await tester.pump(const Duration(milliseconds: 300)); + await tester.tap(find.byIcon(Icons.tune)); + await tester.pump(const Duration(milliseconds: 100)); await tester.tap(find.text('Manage password')); - await tester.pump(const Duration(milliseconds: 300)); + await tester.pump(const Duration(milliseconds: 100)); - var second_password = 'bbb222'; - await tester.enterText( - find.byKey(const Key('current oath password')), second_password); + await tester.enterText(find.byKey(const Key('current oath password')), secondPassword); + await tester.pump(); + await tester.enterText(find.byKey(const Key('new oath password')), thirdPassword); + await tester.pump(); + await tester.enterText(find.byKey(const Key('confirm oath password')), thirdPassword); + await tester.pump(); + + await tester.tap(find.text('Save')); + await tester.pump(const Duration(milliseconds: 100)); + + await tester.pump(const Duration(milliseconds: 1000)); + + /// TODO: verification of state here: see that list of accounts is shown + }); + + testWidgets('OATH: remove thirdPassword', (WidgetTester tester) async { + await tester.pumpWidget(await getAuthenticatorApp()); + await tester.pump(const Duration(milliseconds: 500)); + + var thirdPassword = 'ccc333'; + + await tester.tap(find.byIcon(Icons.tune)); + await tester.pump(const Duration(milliseconds: 100)); + + await tester.tap(find.text('Manage password')); + await tester.pump(const Duration(milliseconds: 100)); + + await tester.enterText(find.byKey(const Key('current oath password')), thirdPassword); await tester.pump(); await tester.tap(find.text('Remove password')); - await tester.pump(const Duration(milliseconds: 300)); - /// TODO: verification of state here: restarting app and entering password - await tester.pump(const Duration(seconds: 3)); + /// TODO: verification of state here: see that list of accounts is shown + await tester.pump(const Duration(milliseconds: 1000)); }); }); group('TOTP tests', () { - testWidgets('Add 32 TOTP accounts and reset oath', - (WidgetTester tester) async { + /* + Tests will verify all TOTP functionality, not yet though: + 1. Add 32 TOTP accounts + */ + testWidgets('TOTP: Add 32 accounts', (WidgetTester tester) async { await tester.pumpWidget(await getAuthenticatorApp()); await tester.pump(const Duration(milliseconds: 500)); - expect(find.byType(NoDeviceScreen), findsNothing, - reason: 'No YubiKey connected'); - expect(find.byType(OathScreen), findsOneWidget); - for (var i = 0; i < 32; i += 1) { - await tester.tap(find.byType(FloatingActionButton)); - await tester.pump(const Duration(milliseconds: 300)); - - await tester.tap(find.text('Add account')); - await tester.pump(const Duration(milliseconds: 300)); + await tester.tap(find.byKey(const Key('add oath account'))); + await tester.pump(const Duration(milliseconds: 100)); var issuer = generateRandomIssuer(); var name = generateRandomName(); @@ -153,53 +182,50 @@ void main() { /// this random fails: generateRandomSecret(); await tester.enterText(find.byKey(const Key('issuer')), issuer); - await tester.pump(const Duration(milliseconds: 5)); + await tester.pump(const Duration(milliseconds: 40)); await tester.enterText(find.byKey(const Key('name')), name); - await tester.pump(const Duration(milliseconds: 5)); + await tester.pump(const Duration(milliseconds: 40)); await tester.enterText(find.byKey(const Key('secret')), secret); - await tester.pump(const Duration(milliseconds: 300)); + await tester.pump(const Duration(milliseconds: 100)); await tester.tap(find.byKey(const Key('save_btn'))); - await tester.pump(const Duration(milliseconds: 300)); + await tester.pump(const Duration(milliseconds: 100)); expect(find.byType(OathScreen), findsOneWidget); - await tester.enterText( - find.byKey(const Key('search_accounts')), issuer); + await tester.enterText(find.byKey(const Key('search_accounts')), issuer); - await tester.pump(const Duration(milliseconds: 500)); + await tester.pump(const Duration(milliseconds: 100)); - expect( - find.descendant( - of: find.byType(AccountList), - matching: find.textContaining(issuer)), - findsOneWidget); + expect(find.descendant(of: find.byType(AccountList), matching: find.textContaining(issuer)), findsOneWidget); await tester.pump(const Duration(milliseconds: 50)); } - + await tester.pump(const Duration(milliseconds: 3000)); + /* await tester.tap(find.byType(FloatingActionButton)); - await tester.pump(const Duration(milliseconds: 300)); + await tester.pump(const Duration(milliseconds: 500)); await tester.tap(find.text('Reset OATH')); - await tester.pump(const Duration(milliseconds: 300)); + await tester.pump(const Duration(milliseconds: 500)); await tester.tap(find.text('Reset')); - await tester.pump(const Duration(milliseconds: 300)); + await tester.pump(const Duration(milliseconds: 500)); - await tester.pump(const Duration(seconds: 3)); + */ }); }); + /* group('HOTP tests', () { testWidgets('first HOTP test', (WidgetTester tester) async { await tester.pumpWidget(await getAuthenticatorApp()); await tester.pump(const Duration(milliseconds: 500)); - expect(find.byType(NoDeviceScreen), findsNothing, - reason: 'No YubiKey connected'); expect(find.byType(OathScreen), findsOneWidget); }); }); + + */ } diff --git a/integration_test/reset_key.dart b/integration_test/reset_key.dart index d3ea9eb8..e1e1d9ba 100644 --- a/integration_test/reset_key.dart +++ b/integration_test/reset_key.dart @@ -4,7 +4,6 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; -import 'package:yubico_authenticator/app/views/no_device_screen.dart'; import 'package:yubico_authenticator/oath/views/oath_screen.dart'; import 'test_util.dart'; @@ -23,11 +22,11 @@ String randomPadded() { } String generateRandomIssuer() { - return 'i' + randomPadded(); + return 'i${randomPadded()}'; } String generateRandomName() { - return 'n' + randomPadded(); + return 'n${randomPadded()}'; } String generateRandomSecret() { @@ -46,10 +45,6 @@ void main() { await tester.pumpWidget(await getAuthenticatorApp()); await tester.pump(const Duration(milliseconds: 500)); - expect(find.byType(NoDeviceScreen), findsNothing, - reason: 'No YubiKey connected'); - expect(find.byType(OathScreen), findsOneWidget); - /// QUESTION: I want to click the DrawerItem named 'WebAuthn' | 'Authenticator' /// await tester.tap(find.byType(DrawerItem.titleText == 'WebAuthn')); /// which can be found in main_drawer.dart, how do I make sure I call the right @@ -90,10 +85,6 @@ void main() { await tester.pumpWidget(await getAuthenticatorApp()); await tester.pump(const Duration(milliseconds: 500)); - expect(find.byType(NoDeviceScreen), findsNothing, - reason: 'No YubiKey connected'); - expect(find.byType(OathScreen), findsOneWidget); - /// QUESTION: I want to click the DrawerItem named 'WebAuthn' | 'Authenticator' /// await tester.tap(find.byType(DrawerItem.titleText == 'WebAuthn')); /// which can be found in main_drawer.dart, how do I make sure I call the right @@ -127,8 +118,7 @@ void main() { await tester.pump(const Duration(milliseconds: 30000)); /// The following should report the success, if there are no accounts. - expect(find.byType(OathScreen), findsNothing, - reason: 'FIDO successfully reset.'); + expect(find.byType(OathScreen), findsNothing, reason: 'FIDO successfully reset.'); await tester.pump(const Duration(seconds: 3)); }); diff --git a/lib/oath/views/oath_screen.dart b/lib/oath/views/oath_screen.dart index 0be28141..4dac630e 100755 --- a/lib/oath/views/oath_screen.dart +++ b/lib/oath/views/oath_screen.dart @@ -36,9 +36,8 @@ class OathScreen extends ConsumerWidget { title: const Text('Authenticator'), cause: error, ), - data: (oathState) => oathState.locked - ? _LockedView(devicePath, oathState) - : _UnlockedView(devicePath, oathState), + data: (oathState) => + oathState.locked ? _LockedView(devicePath, oathState) : _UnlockedView(devicePath, oathState), ); } } @@ -64,8 +63,7 @@ class _LockedView extends ConsumerWidget { action: (context) { showDialog( context: context, - builder: (context) => - ManagePasswordDialog(devicePath, oathState), + builder: (context) => ManagePasswordDialog(devicePath, oathState), ); }, ), @@ -124,8 +122,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> { @override Widget build(BuildContext context) { - final isEmpty = ref.watch(credentialListProvider(widget.devicePath) - .select((value) => value?.isEmpty == true)); + final isEmpty = ref.watch(credentialListProvider(widget.devicePath).select((value) => value?.isEmpty == true)); if (isEmpty) { return MessagePage( title: const Text('Authenticator'), @@ -137,8 +134,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> { return Actions( actions: { SearchIntent: CallbackAction(onInvoke: (_) { - searchController.selection = TextSelection( - baseOffset: 0, extentOffset: searchController.text.length); + searchController.selection = TextSelection(baseOffset: 0, extentOffset: searchController.text.length); searchFocus.requestFocus(); return null; }), @@ -191,6 +187,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> { List _buildActions(BuildContext context, bool isEmpty) { return [ OutlinedButton.icon( + key: const Key('add oath account'), style: isEmpty ? AppTheme.primaryOutlinedButtonStyle(context) : null, label: const Text('Add account'), icon: const Icon(Icons.person_add_alt_1), @@ -211,14 +208,12 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> { onPressed: () { showBottomMenu(context, [ MenuAction( - text: - widget.oathState.hasKey ? 'Manage password' : 'Set password', + text: widget.oathState.hasKey ? 'Manage password' : 'Set password', icon: const Icon(Icons.password), action: (context) { showDialog( context: context, - builder: (context) => - ManagePasswordDialog(widget.devicePath, widget.oathState), + builder: (context) => ManagePasswordDialog(widget.devicePath, widget.oathState), ); }, ), @@ -287,6 +282,7 @@ class _UnlockFormState extends ConsumerState<_UnlockForm> { ), const SizedBox(height: 16.0), TextField( + key: const Key('oath password'), controller: _passwordController, autofocus: true, obscureText: _isObscure, @@ -339,6 +335,7 @@ class _UnlockFormState extends ConsumerState<_UnlockForm> { child: Align( alignment: Alignment.centerRight, child: ElevatedButton( + key: const Key('oath unlock'), onPressed: _passwordController.text.isNotEmpty ? _submit : null, child: const Text('Unlock'), ),