From 80510045452ef1fdad517ddc4fb84937dcc336a6 Mon Sep 17 00:00:00 2001 From: Omid Marfavi <21163286+marfavi@users.noreply.github.com> Date: Sun, 4 Jun 2023 13:37:32 +0200 Subject: [PATCH 1/3] test: `secure_storage.dart` --- lib/data/storage/secure_storage.dart | 8 +- lib/service_locator.dart | 5 +- test/data/storage/secure_storage_test.dart | 185 +++++++++++++++++++++ 3 files changed, 194 insertions(+), 4 deletions(-) create mode 100644 test/data/storage/secure_storage_test.dart diff --git a/lib/data/storage/secure_storage.dart b/lib/data/storage/secure_storage.dart index bda489149..535b8984e 100644 --- a/lib/data/storage/secure_storage.dart +++ b/lib/data/storage/secure_storage.dart @@ -10,9 +10,11 @@ class SecureStorage { final FlutterSecureStorage _storage; final Logger _logger; - SecureStorage(this._logger) : _storage = const FlutterSecureStorage(); - - Future get hasToken async => await readToken() != null; + const SecureStorage({ + required FlutterSecureStorage storage, + required Logger logger, + }) : _storage = storage, + _logger = logger; Future saveAuthenticatedUser( String email, diff --git a/lib/service_locator.dart b/lib/service_locator.dart index 2a42634c7..24c5770a8 100644 --- a/lib/service_locator.dart +++ b/lib/service_locator.dart @@ -54,6 +54,7 @@ import 'package:coffeecard/utils/firebase_analytics_event_logging.dart'; import 'package:coffeecard/utils/ignore_value.dart'; import 'package:coffeecard/utils/reactivation_authenticator.dart'; import 'package:firebase_analytics/firebase_analytics.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:get_it/get_it.dart'; import 'package:logger/logger.dart'; @@ -75,7 +76,9 @@ void configureServices() { // Storage ignoreValue( - sl.registerSingleton(SecureStorage(sl())), + sl.registerSingleton( + SecureStorage(storage: const FlutterSecureStorage(), logger: sl()), + ), ); // Authentication diff --git a/test/data/storage/secure_storage_test.dart b/test/data/storage/secure_storage_test.dart new file mode 100644 index 000000000..362c06e0e --- /dev/null +++ b/test/data/storage/secure_storage_test.dart @@ -0,0 +1,185 @@ +import 'package:coffeecard/data/storage/secure_storage.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:logger/logger.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'secure_storage_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec(), MockSpec()]) +void main() { + group('SecureStorage', () { + const emailKey = 'email'; + const tokenKey = 'authentication_token'; + const encodedPasscodeKey = 'encoded_passcode'; + + late SecureStorage secureStorage; + late MockFlutterSecureStorage mockStorage; + late MockLogger mockLogger; + + setUp(() { + mockStorage = MockFlutterSecureStorage(); + mockLogger = MockLogger(); + secureStorage = SecureStorage(storage: mockStorage, logger: mockLogger); + }); + + test( + 'GIVEN user credentials ' + 'WHEN saveAuthenticatedUser is called ' + 'THEN it should save the user credentials to secure storage', + () async { + const email = 'test@example.com'; + const encodedPasscode = 'encodedPasscode'; + const token = 'token'; + + await secureStorage.saveAuthenticatedUser( + email, + encodedPasscode, + token, + ); + + verifyInOrder([ + mockStorage.write(key: emailKey, value: email), + mockStorage.write(key: encodedPasscodeKey, value: encodedPasscode), + mockStorage.write(key: tokenKey, value: token), + mockLogger.d(any), + ]); + }, + ); + + test( + 'GIVEN user credentials in secure storage ' + 'WHEN getAuthenticatedUser is called ' + 'THEN it should return the authenticated user', + () async { + const email = 'test@example.com'; + const token = 'token'; + + when(mockStorage.read(key: emailKey)).thenAnswer((_) async => email); + when(mockStorage.read(key: tokenKey)).thenAnswer((_) async => token); + + final user = await secureStorage.getAuthenticatedUser(); + + expect(user, isNotNull); + expect(user?.email, email); + expect(user?.token, token); + }, + ); + + test( + 'GIVEN missing token in secure storage ' + 'WHEN getAuthenticatedUser is called ' + 'THEN it should return null', + () async { + const email = 'test@example.com'; + + when(mockStorage.read(key: emailKey)).thenAnswer((_) async => email); + when(mockStorage.read(key: tokenKey)).thenAnswer((_) async => null); + + final user = await secureStorage.getAuthenticatedUser(); + + expect(user, isNull); + }, + ); + + test( + 'GIVEN user credentials in secure storage ' + 'WHEN clearAuthenticatedUser is called ' + 'THEN it should remove the user credentials from secure storage', + () async { + when(mockStorage.read(key: emailKey)) + .thenAnswer((_) async => 'test@example.com'); + when(mockStorage.read(key: encodedPasscodeKey)) + .thenAnswer((_) async => 'encodedPasscode'); + when(mockStorage.read(key: tokenKey)).thenAnswer((_) async => 'token'); + + await secureStorage.clearAuthenticatedUser(); + + verifyInOrder([ + mockStorage.delete(key: emailKey), + mockStorage.delete(key: encodedPasscodeKey), + mockStorage.delete(key: tokenKey), + mockLogger.d(any), + ]); + }, + ); + + test( + 'GIVEN missing email in secure storage ' + 'WHEN clearAuthenticatedUser is called ' + 'THEN it should not remove any user credentials', + () async { + when(mockStorage.read(key: emailKey)).thenAnswer((_) async => null); + + await secureStorage.clearAuthenticatedUser(); + + verifyInOrder([ + mockStorage.read(key: emailKey), + mockStorage.read(key: tokenKey), + ]); + verifyNever(mockStorage.delete(key: anyNamed('key'))); + }, + ); + + test( + 'GIVEN a new token ' + 'WHEN updateToken is called ' + 'THEN it should update the token in secure storage', + () async { + const token = 'new_token'; + + await secureStorage.updateToken(token); + + verify(mockStorage.write(key: tokenKey, value: token)); + verify(mockLogger.d('Token updated in Secure Storage')); + }, + ); + + test( + 'GIVEN email stored in secure storage ' + 'WHEN readEmail is called ' + 'THEN it should return the email', + () async { + const email = 'test@example.com'; + + when(mockStorage.read(key: emailKey)).thenAnswer((_) async => email); + + final result = await secureStorage.readEmail(); + + expect(result, email); + }, + ); + + test( + 'GIVEN encoded passcode stored in secure storage ' + 'WHEN readEncodedPasscode is called ' + 'THEN it should return the encoded passcode', + () async { + const encodedPasscode = 'encodedPasscode'; + + when(mockStorage.read(key: encodedPasscodeKey)) + .thenAnswer((_) async => encodedPasscode); + + final result = await secureStorage.readEncodedPasscode(); + + expect(result, encodedPasscode); + }, + ); + + test( + 'GIVEN token stored in secure storage ' + 'WHEN readToken is called ' + 'THEN it should return the token', + () async { + const token = 'token'; + + when(mockStorage.read(key: tokenKey)).thenAnswer((_) async => token); + + final result = await secureStorage.readToken(); + + expect(result, token); + }, + ); + }); +} From 1e6454464a123c06974bb349bcf36194fad20fa0 Mon Sep 17 00:00:00 2001 From: Omid Marfavi <21163286+marfavi@users.noreply.github.com> Date: Tue, 6 Jun 2023 18:18:28 +0200 Subject: [PATCH 2/3] Add no email saved test case --- test/data/storage/secure_storage_test.dart | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/data/storage/secure_storage_test.dart b/test/data/storage/secure_storage_test.dart index 362c06e0e..1f7f2f889 100644 --- a/test/data/storage/secure_storage_test.dart +++ b/test/data/storage/secure_storage_test.dart @@ -181,5 +181,21 @@ void main() { expect(result, token); }, ); + + test( + 'GIVEN no token or email stored in secure storage ' + 'WHEN readToken and readEmail are called ' + 'THEN they should return null', + () async { + when(mockStorage.read(key: emailKey)).thenAnswer((_) async => null); + when(mockStorage.read(key: tokenKey)).thenAnswer((_) async => null); + + final email = await secureStorage.readEmail(); + final token = await secureStorage.readToken(); + + expect(email, isNull); + expect(token, isNull); + }, + ); }); } From f352fed801372272103995b0416d46bedb68f527 Mon Sep 17 00:00:00 2001 From: Omid Marfavi <21163286+marfavi@users.noreply.github.com> Date: Wed, 30 Aug 2023 15:12:46 +0200 Subject: [PATCH 3/3] Remove group --- test/data/storage/secure_storage_test.dart | 374 ++++++++++----------- 1 file changed, 186 insertions(+), 188 deletions(-) diff --git a/test/data/storage/secure_storage_test.dart b/test/data/storage/secure_storage_test.dart index 1f7f2f889..7e47356fb 100644 --- a/test/data/storage/secure_storage_test.dart +++ b/test/data/storage/secure_storage_test.dart @@ -9,193 +9,191 @@ import 'secure_storage_test.mocks.dart'; @GenerateNiceMocks([MockSpec(), MockSpec()]) void main() { - group('SecureStorage', () { - const emailKey = 'email'; - const tokenKey = 'authentication_token'; - const encodedPasscodeKey = 'encoded_passcode'; - - late SecureStorage secureStorage; - late MockFlutterSecureStorage mockStorage; - late MockLogger mockLogger; - - setUp(() { - mockStorage = MockFlutterSecureStorage(); - mockLogger = MockLogger(); - secureStorage = SecureStorage(storage: mockStorage, logger: mockLogger); - }); - - test( - 'GIVEN user credentials ' - 'WHEN saveAuthenticatedUser is called ' - 'THEN it should save the user credentials to secure storage', - () async { - const email = 'test@example.com'; - const encodedPasscode = 'encodedPasscode'; - const token = 'token'; - - await secureStorage.saveAuthenticatedUser( - email, - encodedPasscode, - token, - ); - - verifyInOrder([ - mockStorage.write(key: emailKey, value: email), - mockStorage.write(key: encodedPasscodeKey, value: encodedPasscode), - mockStorage.write(key: tokenKey, value: token), - mockLogger.d(any), - ]); - }, - ); - - test( - 'GIVEN user credentials in secure storage ' - 'WHEN getAuthenticatedUser is called ' - 'THEN it should return the authenticated user', - () async { - const email = 'test@example.com'; - const token = 'token'; - - when(mockStorage.read(key: emailKey)).thenAnswer((_) async => email); - when(mockStorage.read(key: tokenKey)).thenAnswer((_) async => token); - - final user = await secureStorage.getAuthenticatedUser(); - - expect(user, isNotNull); - expect(user?.email, email); - expect(user?.token, token); - }, - ); - - test( - 'GIVEN missing token in secure storage ' - 'WHEN getAuthenticatedUser is called ' - 'THEN it should return null', - () async { - const email = 'test@example.com'; - - when(mockStorage.read(key: emailKey)).thenAnswer((_) async => email); - when(mockStorage.read(key: tokenKey)).thenAnswer((_) async => null); - - final user = await secureStorage.getAuthenticatedUser(); - - expect(user, isNull); - }, - ); - - test( - 'GIVEN user credentials in secure storage ' - 'WHEN clearAuthenticatedUser is called ' - 'THEN it should remove the user credentials from secure storage', - () async { - when(mockStorage.read(key: emailKey)) - .thenAnswer((_) async => 'test@example.com'); - when(mockStorage.read(key: encodedPasscodeKey)) - .thenAnswer((_) async => 'encodedPasscode'); - when(mockStorage.read(key: tokenKey)).thenAnswer((_) async => 'token'); - - await secureStorage.clearAuthenticatedUser(); - - verifyInOrder([ - mockStorage.delete(key: emailKey), - mockStorage.delete(key: encodedPasscodeKey), - mockStorage.delete(key: tokenKey), - mockLogger.d(any), - ]); - }, - ); - - test( - 'GIVEN missing email in secure storage ' - 'WHEN clearAuthenticatedUser is called ' - 'THEN it should not remove any user credentials', - () async { - when(mockStorage.read(key: emailKey)).thenAnswer((_) async => null); - - await secureStorage.clearAuthenticatedUser(); - - verifyInOrder([ - mockStorage.read(key: emailKey), - mockStorage.read(key: tokenKey), - ]); - verifyNever(mockStorage.delete(key: anyNamed('key'))); - }, - ); - - test( - 'GIVEN a new token ' - 'WHEN updateToken is called ' - 'THEN it should update the token in secure storage', - () async { - const token = 'new_token'; - - await secureStorage.updateToken(token); - - verify(mockStorage.write(key: tokenKey, value: token)); - verify(mockLogger.d('Token updated in Secure Storage')); - }, - ); - - test( - 'GIVEN email stored in secure storage ' - 'WHEN readEmail is called ' - 'THEN it should return the email', - () async { - const email = 'test@example.com'; - - when(mockStorage.read(key: emailKey)).thenAnswer((_) async => email); - - final result = await secureStorage.readEmail(); - - expect(result, email); - }, - ); - - test( - 'GIVEN encoded passcode stored in secure storage ' - 'WHEN readEncodedPasscode is called ' - 'THEN it should return the encoded passcode', - () async { - const encodedPasscode = 'encodedPasscode'; - - when(mockStorage.read(key: encodedPasscodeKey)) - .thenAnswer((_) async => encodedPasscode); - - final result = await secureStorage.readEncodedPasscode(); - - expect(result, encodedPasscode); - }, - ); - - test( - 'GIVEN token stored in secure storage ' - 'WHEN readToken is called ' - 'THEN it should return the token', - () async { - const token = 'token'; - - when(mockStorage.read(key: tokenKey)).thenAnswer((_) async => token); - - final result = await secureStorage.readToken(); - - expect(result, token); - }, - ); - - test( - 'GIVEN no token or email stored in secure storage ' - 'WHEN readToken and readEmail are called ' - 'THEN they should return null', - () async { - when(mockStorage.read(key: emailKey)).thenAnswer((_) async => null); - when(mockStorage.read(key: tokenKey)).thenAnswer((_) async => null); - - final email = await secureStorage.readEmail(); - final token = await secureStorage.readToken(); - - expect(email, isNull); - expect(token, isNull); - }, - ); + const emailKey = 'email'; + const tokenKey = 'authentication_token'; + const encodedPasscodeKey = 'encoded_passcode'; + + late SecureStorage secureStorage; + late MockFlutterSecureStorage mockStorage; + late MockLogger mockLogger; + + setUp(() { + mockStorage = MockFlutterSecureStorage(); + mockLogger = MockLogger(); + secureStorage = SecureStorage(storage: mockStorage, logger: mockLogger); }); + + test( + 'GIVEN user credentials ' + 'WHEN saveAuthenticatedUser is called ' + 'THEN it should save the user credentials to secure storage', + () async { + const email = 'test@example.com'; + const encodedPasscode = 'encodedPasscode'; + const token = 'token'; + + await secureStorage.saveAuthenticatedUser( + email, + encodedPasscode, + token, + ); + + verifyInOrder([ + mockStorage.write(key: emailKey, value: email), + mockStorage.write(key: encodedPasscodeKey, value: encodedPasscode), + mockStorage.write(key: tokenKey, value: token), + mockLogger.d(any), + ]); + }, + ); + + test( + 'GIVEN user credentials in secure storage ' + 'WHEN getAuthenticatedUser is called ' + 'THEN it should return the authenticated user', + () async { + const email = 'test@example.com'; + const token = 'token'; + + when(mockStorage.read(key: emailKey)).thenAnswer((_) async => email); + when(mockStorage.read(key: tokenKey)).thenAnswer((_) async => token); + + final user = await secureStorage.getAuthenticatedUser(); + + expect(user, isNotNull); + expect(user?.email, email); + expect(user?.token, token); + }, + ); + + test( + 'GIVEN missing token in secure storage ' + 'WHEN getAuthenticatedUser is called ' + 'THEN it should return null', + () async { + const email = 'test@example.com'; + + when(mockStorage.read(key: emailKey)).thenAnswer((_) async => email); + when(mockStorage.read(key: tokenKey)).thenAnswer((_) async => null); + + final user = await secureStorage.getAuthenticatedUser(); + + expect(user, isNull); + }, + ); + + test( + 'GIVEN user credentials in secure storage ' + 'WHEN clearAuthenticatedUser is called ' + 'THEN it should remove the user credentials from secure storage', + () async { + when(mockStorage.read(key: emailKey)) + .thenAnswer((_) async => 'test@example.com'); + when(mockStorage.read(key: encodedPasscodeKey)) + .thenAnswer((_) async => 'encodedPasscode'); + when(mockStorage.read(key: tokenKey)).thenAnswer((_) async => 'token'); + + await secureStorage.clearAuthenticatedUser(); + + verifyInOrder([ + mockStorage.delete(key: emailKey), + mockStorage.delete(key: encodedPasscodeKey), + mockStorage.delete(key: tokenKey), + mockLogger.d(any), + ]); + }, + ); + + test( + 'GIVEN missing email in secure storage ' + 'WHEN clearAuthenticatedUser is called ' + 'THEN it should not remove any user credentials', + () async { + when(mockStorage.read(key: emailKey)).thenAnswer((_) async => null); + + await secureStorage.clearAuthenticatedUser(); + + verifyInOrder([ + mockStorage.read(key: emailKey), + mockStorage.read(key: tokenKey), + ]); + verifyNever(mockStorage.delete(key: anyNamed('key'))); + }, + ); + + test( + 'GIVEN a new token ' + 'WHEN updateToken is called ' + 'THEN it should update the token in secure storage', + () async { + const token = 'new_token'; + + await secureStorage.updateToken(token); + + verify(mockStorage.write(key: tokenKey, value: token)); + verify(mockLogger.d('Token updated in Secure Storage')); + }, + ); + + test( + 'GIVEN email stored in secure storage ' + 'WHEN readEmail is called ' + 'THEN it should return the email', + () async { + const email = 'test@example.com'; + + when(mockStorage.read(key: emailKey)).thenAnswer((_) async => email); + + final result = await secureStorage.readEmail(); + + expect(result, email); + }, + ); + + test( + 'GIVEN encoded passcode stored in secure storage ' + 'WHEN readEncodedPasscode is called ' + 'THEN it should return the encoded passcode', + () async { + const encodedPasscode = 'encodedPasscode'; + + when(mockStorage.read(key: encodedPasscodeKey)) + .thenAnswer((_) async => encodedPasscode); + + final result = await secureStorage.readEncodedPasscode(); + + expect(result, encodedPasscode); + }, + ); + + test( + 'GIVEN token stored in secure storage ' + 'WHEN readToken is called ' + 'THEN it should return the token', + () async { + const token = 'token'; + + when(mockStorage.read(key: tokenKey)).thenAnswer((_) async => token); + + final result = await secureStorage.readToken(); + + expect(result, token); + }, + ); + + test( + 'GIVEN no token or email stored in secure storage ' + 'WHEN readToken and readEmail are called ' + 'THEN they should return null', + () async { + when(mockStorage.read(key: emailKey)).thenAnswer((_) async => null); + when(mockStorage.read(key: tokenKey)).thenAnswer((_) async => null); + + final email = await secureStorage.readEmail(); + final token = await secureStorage.readToken(); + + expect(email, isNull); + expect(token, isNull); + }, + ); }