From 5dff4693fd524158288ce331450dd6c4d48179db Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 17 Oct 2023 09:11:36 -0700 Subject: [PATCH] fix(authenticator): fix validate before trim bug (#3809) * chore: added .trim() to other fields in the authenticator state instead of just password * chore: moved trim before validation in methods where there is a string * chore: revert trim additions from before and correctly added trim to the email and username validator * chore: revert amplify_authenticator_test changes * chore: revert all changes from authenticator_state.dart * chore: saving tests to clean up local repo * chore: completed auth form tests for API call * chore: removed unnecessary variables * chore: removed unnecessary duplication of receiver * chore: removed the unnecessary captured password * chore: add signInPage.expectStep back in * chore: trimmed password, verification, phone --- .../lib/src/utils/validators.dart | 19 +++- .../test/sign_in_form_test.dart | 90 ++++++++++++++++- .../test/sign_up_form_test.dart | 99 +++++++++++++++++++ 3 files changed, 200 insertions(+), 8 deletions(-) diff --git a/packages/authenticator/amplify_authenticator/lib/src/utils/validators.dart b/packages/authenticator/amplify_authenticator/lib/src/utils/validators.dart index 46f5b7aba8..942742cbaf 100644 --- a/packages/authenticator/amplify_authenticator/lib/src/utils/validators.dart +++ b/packages/authenticator/amplify_authenticator/lib/src/utils/validators.dart @@ -44,7 +44,7 @@ FormFieldValidator usernameValidator({ InputResolverKey.usernameEmpty, ); } - + input = input.trim(); if (!usernameRegex.hasMatch(input)) { return inputResolver.resolve( context, @@ -85,6 +85,7 @@ FormFieldValidator Function(BuildContext) validateNewPassword({ InputResolverKey.passwordEmpty, ); } + password = password.trim(); if (passwordProtectionSettings == null) { return null; } @@ -128,7 +129,9 @@ FormFieldValidator validatePasswordConfirmation( context, InputResolverKey.passwordConfirmationEmpty, ); - } else if (getPassword() != passwordConfirmation.trim()) { + } + passwordConfirmation = passwordConfirmation.trim(); + if (getPassword() != passwordConfirmation.trim()) { return inputResolver.resolve( context, InputResolverKey.passwordsDoNotMatch, @@ -152,7 +155,9 @@ FormFieldValidator validatePhoneNumber({ context, InputResolverKey.phoneNumberEmpty, ); - } else if (!phoneNumberRegex.hasMatch(phoneNumber)) { + } + phoneNumber = phoneNumber.trim(); + if (!phoneNumberRegex.hasMatch(phoneNumber)) { return inputResolver.resolve(context, InputResolverKey.phoneNumberFormat); } return null; @@ -173,7 +178,9 @@ FormFieldValidator validateEmail({ context, InputResolverKey.emailEmpty, ); - } else if (!emailRegex.hasMatch(email)) { + } + email = email.trim(); + if (!emailRegex.hasMatch(email)) { return inputResolver.resolve(context, InputResolverKey.emailFormat); } return null; @@ -191,7 +198,9 @@ FormFieldValidator validateCode({ context, InputResolverKey.verificationCodeEmpty, ); - } else if (!_codeRegex.hasMatch(code)) { + } + code = code.trim(); + if (!_codeRegex.hasMatch(code)) { return inputResolver.resolve( context, InputResolverKey.verificationCodeFormat, diff --git a/packages/authenticator/amplify_authenticator/test/sign_in_form_test.dart b/packages/authenticator/amplify_authenticator/test/sign_in_form_test.dart index b337a4589e..6a9d11b6ad 100644 --- a/packages/authenticator/amplify_authenticator/test/sign_in_form_test.dart +++ b/packages/authenticator/amplify_authenticator/test/sign_in_form_test.dart @@ -1,13 +1,52 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; import 'package:amplify_authenticator/amplify_authenticator.dart'; +import 'package:amplify_authenticator/src/services/amplify_auth_service.dart'; import 'package:amplify_authenticator_test/amplify_authenticator_test.dart'; +import 'package:amplify_integration_test/amplify_integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockAuthService extends Mock implements AmplifyAuthService { + String? capturedUsername; + + @override + Future signIn( + String username, + String password, { + SignInOptions? options, + }) { + capturedUsername = username; + return Future.value( + const CognitoSignInResult( + isSignedIn: true, + nextStep: AuthNextSignInStep(signInStep: AuthSignInStep.done), + ), + ); + } +} + +class MockAuthPlugin extends AmplifyAuthCognitoStub { + MockAuthPlugin(this.authService); + final MockAuthService authService; + + @override + Future signIn({ + required String username, + String? password, + SignInOptions? options, + }) { + return authService.signIn(username, password ?? ''); + } +} void main() { TestWidgetsFlutterBinding.ensureInitialized(); + setUp(TestWidgetsFlutterBinding.ensureInitialized); + group('Sign In View', () { group('navigation', () { testWidgets('via TabBar', (tester) async { @@ -15,8 +54,6 @@ void main() { await tester.pumpAndSettle(); final signInPage = SignInPage(tester: tester); - // ignore: cascade_invocations - signInPage.expectStep(AuthenticatorStep.signIn); // Go to Sign Up await tester.tap(signInPage.signUpTab); @@ -39,7 +76,8 @@ void main() { await tester.pumpAndSettle(); final signInPage = SignInPage(tester: tester); - + // ignore: cascade_invocations + signInPage.expectStep(AuthenticatorStep.signIn); await signInPage.submitSignIn(); final usernameFieldError = find.descendant( @@ -79,6 +117,52 @@ void main() { expect(usernameFieldError, findsOneWidget); }, ); + + testWidgets( + 'trims the username field before validation', + (tester) async { + await tester.pumpWidget(const MockAuthenticatorApp()); + await tester.pumpAndSettle(); + + final signInPage = SignInPage(tester: tester); + + await signInPage.enterUsername('user@example.com '); + await signInPage.enterPassword('Password123'); + + await signInPage.submitSignIn(); + + final usernameFieldError = find.descendant( + of: signInPage.usernameField, + matching: find.text('Invalid email format.'), + ); + + expect(usernameFieldError, findsNothing); + }, + ); + + testWidgets( + 'ensures email passed to the API is trimmed', + (tester) async { + final mockAuthService = MockAuthService(); + final mockAuthPlugin = MockAuthPlugin(mockAuthService); + final app = MockAuthenticatorApp(authPlugin: mockAuthPlugin); + + await tester.pumpWidget(app); + await tester.pumpAndSettle(); + + final signInPage = SignInPage(tester: tester); + + // Enter email with trailing space and a valid password + await signInPage.enterUsername('user@example.com '); + await signInPage.enterPassword('Password123'); + + await signInPage.submitSignIn(); + await tester.pumpAndSettle(); + + // Verify the email was trimmed before being passed to the signIn method + expect(mockAuthService.capturedUsername, 'user@example.com'); + }, + ); }); }); } diff --git a/packages/authenticator/amplify_authenticator/test/sign_up_form_test.dart b/packages/authenticator/amplify_authenticator/test/sign_up_form_test.dart index 8a83833686..119bf106b7 100644 --- a/packages/authenticator/amplify_authenticator/test/sign_up_form_test.dart +++ b/packages/authenticator/amplify_authenticator/test/sign_up_form_test.dart @@ -3,10 +3,55 @@ import 'dart:convert'; +import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; import 'package:amplify_authenticator/amplify_authenticator.dart'; +import 'package:amplify_authenticator/src/services/amplify_auth_service.dart'; import 'package:amplify_authenticator_test/amplify_authenticator_test.dart'; import 'package:amplify_core/amplify_core.dart'; +import 'package:amplify_integration_test/amplify_integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockAuthService extends Mock implements AmplifyAuthService { + String? capturedUsername; + + @override + Future signUp( + String username, + String password, + Map attributes, + ) { + capturedUsername = username; + // Return mock result + return Future.value( + const CognitoSignUpResult( + isSignUpComplete: true, + nextStep: AuthNextSignUpStep(signUpStep: AuthSignUpStep.done), + ), + ); + } +} + +class MockAuthPlugin extends AmplifyAuthCognitoStub { + MockAuthPlugin(this.authService); + + final MockAuthService authService; + + final attributes = {}; + + @override + Future signUp({ + required String username, + required String password, + SignUpOptions? options, + }) { + return authService.signUp( + username, + password, + attributes, + ); + } +} void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -172,6 +217,60 @@ void main() { expect(passwordFieldErrorLine4, findsOneWidget); }, ); + + testWidgets( + 'trims the username field before validation', + (tester) async { + await tester.pumpWidget( + const MockAuthenticatorApp( + initialStep: AuthenticatorStep.signUp, + ), + ); + await tester.pumpAndSettle(); + + final signInPage = SignUpPage(tester: tester); + + await signInPage.enterUsername('user@example.com '); + await signInPage.enterPassword('Password123'); + + await signInPage.submitSignUp(); + + final usernameFieldError = find.descendant( + of: signInPage.usernameField, + matching: find.text('Invalid email format.'), + ); + + expect(usernameFieldError, findsNothing); + }, + ); + + testWidgets( + 'ensures email passed to the API is trimmed', + (tester) async { + final mockAuthService = MockAuthService(); + final mockAuthPlugin = MockAuthPlugin(mockAuthService); + final app = MockAuthenticatorApp( + authPlugin: mockAuthPlugin, + initialStep: AuthenticatorStep.signUp, + ); + + await tester.pumpWidget(app); + await tester.pumpAndSettle(); + + final signUpPage = SignUpPage(tester: tester); + + // Enter email with trailing space + await signUpPage.enterUsername('user@example.com '); + await signUpPage.enterPassword('Password123'); + await signUpPage.enterPasswordConfirmation('Password123'); + + await signUpPage.submitSignUp(); + await tester.pumpAndSettle(); + + // Verify the email was trimmed before being passed to signUp + expect(mockAuthService.capturedUsername, 'user@example.com'); + }, + ); }); }); }