diff --git a/packages/leaderboard_repository/lib/src/leaderboard_repository.dart b/packages/leaderboard_repository/lib/src/leaderboard_repository.dart index 7491112b..af7383b7 100644 --- a/packages/leaderboard_repository/lib/src/leaderboard_repository.dart +++ b/packages/leaderboard_repository/lib/src/leaderboard_repository.dart @@ -72,6 +72,20 @@ class FetchPlayerRankingException extends LeaderboardException { ); } +/// {@template fetch_prohibited_initials_exception} +/// Exception thrown when failure occurs while fetching prohibited initials. +/// {@endtemplate} +class FetchProhibitedInitialsException extends LeaderboardException { + /// {@macro fetch_prohibited_initials_exception} + const FetchProhibitedInitialsException( + Object error, + StackTrace stackTrace, + ) : super( + error, + stackTrace, + ); +} + /// {@template leaderboard_repository} /// Repository to access leaderboard data in Firebase Cloud Firestore. /// {@endtemplate} @@ -153,14 +167,28 @@ class LeaderboardRepository { } } - /// Checks if the given [username] is allowed. The [username] is not allowed - /// if it is a bad word. - Future isUsernameAllowed({required String username}) async { - // TODO(jonathandaniels-vgv): load this list of bad words from an endpoint - final badWords = ['badword']; - final filteredUsername = username.trim().toLowerCase(); - final isUsernameABadWord = badWords.contains(filteredUsername); - final isUsernameAllowed = !isUsernameABadWord; - return isUsernameAllowed; + /// Determines if the given [initials] are allowed. + Future areInitialsAllowed({required String initials}) async { + // Initials can only be three uppercase A-Z letters + final initialsRegex = RegExp(r'^[A-Z]{3}$'); + if (!initialsRegex.hasMatch(initials)) { + return false; + } + + try { + final document = await _firebaseFirestore + .collection('prohibitedInitials') + .doc('list') + .get(); + final prohibitedInitials = List.from( + document.get('prohibitedInitials') as List, + ); + final isProhibited = prohibitedInitials.contains( + initials.trim().toLowerCase(), + ); + return !isProhibited; + } on Exception catch (error, stackTrace) { + throw FetchProhibitedInitialsException(error, stackTrace); + } } } diff --git a/packages/leaderboard_repository/test/src/leaderboard_repository_test.dart b/packages/leaderboard_repository/test/src/leaderboard_repository_test.dart index abc006fe..8ecb7103 100644 --- a/packages/leaderboard_repository/test/src/leaderboard_repository_test.dart +++ b/packages/leaderboard_repository/test/src/leaderboard_repository_test.dart @@ -21,6 +21,9 @@ class MockQueryDocumentSnapshot extends Mock class MockDocumentReference extends Mock implements DocumentReference> {} +class MockDocumentSnapshot extends Mock + implements DocumentSnapshot> {} + void main() { group('LeaderboardRepository', () { late FirebaseFirestore firestore; @@ -224,17 +227,32 @@ void main() { }); }); - group('isUsernameAllowed', () { + group('areInitialsAllowed', () { late LeaderboardRepository leaderboardRepository; + late CollectionReference> collectionReference; + late DocumentReference> documentReference; + late DocumentSnapshot> documentSnapshot; setUp(() async { + collectionReference = MockCollectionReference(); + documentReference = MockDocumentReference(); + documentSnapshot = MockDocumentSnapshot(); leaderboardRepository = LeaderboardRepository(firestore); + + when(() => firestore.collection('prohibitedInitials')) + .thenReturn(collectionReference); + when(() => collectionReference.doc('list')) + .thenReturn(documentReference); + when(() => documentReference.get()) + .thenAnswer((_) async => documentSnapshot); + when(() => documentSnapshot.get('prohibitedInitials')) + .thenReturn(['bad']); }); - test('returns true if username is not a bad word', () async { + test('returns true if initials are three letters and allowed', () async { final isUsernameAllowedResponse = - await leaderboardRepository.isUsernameAllowed( - username: 'goodword', + await leaderboardRepository.areInitialsAllowed( + initials: 'ABC', ); expect( isUsernameAllowedResponse, @@ -242,27 +260,60 @@ void main() { ); }); - test('returns false if username is a bad word', () async { - final isUsernameAllowedResponse = - await leaderboardRepository.isUsernameAllowed( - username: 'badword', - ); - expect( - isUsernameAllowedResponse, - isFalse, - ); - }); + test( + 'returns false if initials are shorter than 3 characters', + () async { + final areInitialsAllowedResponse = + await leaderboardRepository.areInitialsAllowed(initials: 'AB'); + expect(areInitialsAllowedResponse, isFalse); + }, + ); - test('bad word detection should be case insensitive', () async { - final isUsernameAllowedResponse = - await leaderboardRepository.isUsernameAllowed( - username: ' bAdWoRd ', - ); - expect( - isUsernameAllowedResponse, - isFalse, - ); + test( + 'returns false if initials are longer than 3 characters', + () async { + final areInitialsAllowedResponse = + await leaderboardRepository.areInitialsAllowed(initials: 'ABCD'); + expect(areInitialsAllowedResponse, isFalse); + }, + ); + + test( + 'returns false if initials contain a lowercase letter', + () async { + final areInitialsAllowedResponse = + await leaderboardRepository.areInitialsAllowed(initials: 'AbC'); + expect(areInitialsAllowedResponse, isFalse); + }, + ); + + test( + 'returns false if initials contain a special character', + () async { + final areInitialsAllowedResponse = + await leaderboardRepository.areInitialsAllowed(initials: 'A@C'); + expect(areInitialsAllowedResponse, isFalse); + }, + ); + + test('returns false if initials are forbidden', () async { + final areInitialsAllowedResponse = + await leaderboardRepository.areInitialsAllowed(initials: 'BAD'); + expect(areInitialsAllowedResponse, isFalse); }); + + test( + 'throws FetchProhibitedInitialsException when Exception occurs ' + 'when trying to retrieve information from firestore', + () async { + when(() => firestore.collection('prohibitedInitials')) + .thenThrow(Exception('oops')); + expect( + () => leaderboardRepository.areInitialsAllowed(initials: 'ABC'), + throwsA(isA()), + ); + }, + ); }); }); }