From 944c0c113184119a97c632c8734ec275f6c490a7 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 26 Jun 2024 20:16:44 -0500 Subject: [PATCH] fix: add userId to Word --- .../lib/src/crossword_repository.dart | 9 ++ .../test/src/crossword_repository_test.dart | 92 +++++++++++++++++-- .../game_domain/lib/src/models/word.dart | 8 ++ .../game_domain/lib/src/models/word.g.dart | 2 + .../test/src/models/board_section_test.dart | 4 + .../test/src/models/word_test.dart | 1 + api/routes/game/answer.dart | 1 + 7 files changed, 109 insertions(+), 8 deletions(-) diff --git a/api/packages/crossword_repository/lib/src/crossword_repository.dart b/api/packages/crossword_repository/lib/src/crossword_repository.dart index 72749feaf..c55f6505f 100644 --- a/api/packages/crossword_repository/lib/src/crossword_repository.dart +++ b/api/packages/crossword_repository/lib/src/crossword_repository.dart @@ -81,6 +81,7 @@ class CrosswordRepository { /// solvedTimestamp attribute. /// The second value returns true if the answer was previously answered. Future<(bool, bool)> answerWord( + String userId, String wordId, Mascot mascot, String userAnswer, @@ -134,6 +135,13 @@ class CrosswordRepository { ); } + if (word.userId == userId) { + throw CrosswordRepositoryException( + 'Word with id $wordId was already solved by current user', + StackTrace.current, + ); + } + // The word has been solved previously if (word.solvedTimestamp != null) { return (true, true); @@ -143,6 +151,7 @@ class CrosswordRepository { answer: correctAnswer.answer, solvedTimestamp: clock.now().millisecondsSinceEpoch, mascot: mascot, + userId: userId, ); updatedSection = updatedSection.copyWith( words: [...section.words..remove(word), solvedWord], diff --git a/api/packages/crossword_repository/test/src/crossword_repository_test.dart b/api/packages/crossword_repository/test/src/crossword_repository_test.dart index 074802e5f..ad7906f1a 100644 --- a/api/packages/crossword_repository/test/src/crossword_repository_test.dart +++ b/api/packages/crossword_repository/test/src/crossword_repository_test.dart @@ -155,6 +155,7 @@ void main() { axis: WordAxis.horizontal, answer: 'hap y', clue: '', + userId: 'userId', ); final word4 = Word( @@ -296,7 +297,12 @@ void main() { final time = DateTime.now(); final clock = Clock.fixed(time); await withClock(clock, () async { - final valid = await repository.answerWord('4', Mascot.dino, 'solved'); + final valid = await repository.answerWord( + 'userId', + '4', + Mascot.dino, + 'solved', + ); expect(valid, (true, true)); }); }); @@ -306,8 +312,12 @@ void main() { final time = DateTime.now(); final clock = Clock.fixed(time); await withClock(clock, () async { - final valid = - await repository.answerWord('1', Mascot.dino, 'flutter'); + final valid = await repository.answerWord( + 'userId', + '1', + Mascot.dino, + 'flutter', + ); expect(valid, (true, false)); verify( @@ -339,6 +349,7 @@ void main() { solvedTimestamp: time.millisecondsSinceEpoch, mascot: Mascot.dino, answer: 'flutter', + userId: 'userId', ) .toJson(), word2.copyWith(answer: 'bl ').toJson(), @@ -351,7 +362,12 @@ void main() { }); test('returns (false, false) if answer is incorrect', () async { - final valid = await repository.answerWord('1', Mascot.dino, 'android'); + final valid = await repository.answerWord( + 'userId', + '1', + Mascot.dino, + 'android', + ); expect(valid, (false, false)); }); @@ -362,7 +378,12 @@ void main() { () => dbClient.getById(answersCollection, 'fake'), ).thenAnswer((_) async => null); expect( - () => repository.answerWord('fake', Mascot.dino, 'flutter'), + () => repository.answerWord( + 'userId', + 'fake', + Mascot.dino, + 'flutter', + ), throwsA(isA()), ); }, @@ -395,7 +416,12 @@ void main() { ).thenAnswer((_) async => answersRecord); expect( - () => repository.answerWord('3', Mascot.sparky, 'happy'), + () => repository.answerWord( + 'userId', + '3', + Mascot.sparky, + 'happy', + ), throwsA(isA()), ); }, @@ -412,7 +438,12 @@ void main() { ).thenAnswer((_) async => []); expect( - () => repository.answerWord('1', Mascot.dino, 'flutter'), + () => repository.answerWord( + 'userId', + '1', + Mascot.dino, + 'flutter', + ), throwsA(isA()), ); }, @@ -434,7 +465,52 @@ void main() { () => dbClient.getById(answersCollection, 'fake'), ).thenAnswer((_) async => answersRecord); expect( - () => repository.answerWord('fake', Mascot.dino, 'flutter'), + () => repository.answerWord( + 'userId', + 'fake', + Mascot.dino, + 'flutter', + ), + throwsA(isA()), + ); + }, + ); + + test( + 'throws $CrosswordRepositoryException if user has already solved word', + () async { + final answersRecord = _MockDbEntityRecord(); + when(() => answersRecord.id).thenReturn('3'); + when(() => answersRecord.data).thenReturn({ + 'userId': 'userId', + 'answer': 'happy', + 'sections': [ + {'x': 1, 'y': 1}, + ], + 'collidedWords': >[ + { + 'wordId': '6', + 'position': 1, + 'character': 'l', + 'sections': [ + {'x': 1, 'y': 1}, + {'x': 1, 'y': 2}, + ], + } + ], + }); + + when( + () => dbClient.getById(answersCollection, '3'), + ).thenAnswer((_) async => answersRecord); + + expect( + () => repository.answerWord( + 'userId', + '3', + Mascot.sparky, + 'happy', + ), throwsA(isA()), ); }, diff --git a/api/packages/game_domain/lib/src/models/word.dart b/api/packages/game_domain/lib/src/models/word.dart index 90e9197db..843f5ec76 100644 --- a/api/packages/game_domain/lib/src/models/word.dart +++ b/api/packages/game_domain/lib/src/models/word.dart @@ -18,6 +18,7 @@ class Word extends Equatable { required this.answer, this.solvedTimestamp, this.mascot, + this.userId, }); /// {@macro word} @@ -68,6 +69,10 @@ class Word extends Equatable { @JsonKey() final Mascot? mascot; + /// The user id that first solved the word. + @JsonKey() + final String? userId; + /// Returns the solved characters with the index position and character /// solved. Map get solvedCharacters { @@ -96,6 +101,7 @@ class Word extends Equatable { String? answer, int? solvedTimestamp, Mascot? mascot, + String? userId, }) { return Word( id: id ?? this.id, @@ -105,6 +111,7 @@ class Word extends Equatable { answer: answer ?? this.answer, solvedTimestamp: solvedTimestamp ?? this.solvedTimestamp, mascot: mascot ?? this.mascot, + userId: userId ?? this.userId, ); } @@ -117,6 +124,7 @@ class Word extends Equatable { answer, solvedTimestamp, mascot, + userId, ]; } diff --git a/api/packages/game_domain/lib/src/models/word.g.dart b/api/packages/game_domain/lib/src/models/word.g.dart index fa231e11f..3084e39a2 100644 --- a/api/packages/game_domain/lib/src/models/word.g.dart +++ b/api/packages/game_domain/lib/src/models/word.g.dart @@ -15,6 +15,7 @@ Word _$WordFromJson(Map json) => Word( answer: json['answer'] as String, solvedTimestamp: (json['solvedTimestamp'] as num?)?.toInt(), mascot: $enumDecodeNullable(_$MascotEnumMap, json['mascot']), + userId: json['userId'] as String?, ); Map _$WordToJson(Word instance) => { @@ -25,6 +26,7 @@ Map _$WordToJson(Word instance) => { 'answer': instance.answer, 'solvedTimestamp': instance.solvedTimestamp, 'mascot': _$MascotEnumMap[instance.mascot], + 'userId': instance.userId, }; const _$WordAxisEnumMap = { diff --git a/api/packages/game_domain/test/src/models/board_section_test.dart b/api/packages/game_domain/test/src/models/board_section_test.dart index 3d70dda58..9fb8a9c4b 100644 --- a/api/packages/game_domain/test/src/models/board_section_test.dart +++ b/api/packages/game_domain/test/src/models/board_section_test.dart @@ -19,6 +19,7 @@ void main() { clue: 'clue', solvedTimestamp: 1234, mascot: Mascot.android, + userId: 'userId', ), ], ); @@ -37,6 +38,7 @@ void main() { 'clue': 'clue', 'solvedTimestamp': 1234, 'mascot': 'android', + 'userId': 'userId', }, ], }), @@ -56,6 +58,7 @@ void main() { 'clue': 'clue', 'solvedTimestamp': 1234, 'mascot': 'android', + 'userId': 'userId', }, ], }; @@ -75,6 +78,7 @@ void main() { clue: 'clue', solvedTimestamp: 1234, mascot: Mascot.android, + userId: 'userId', ), ], ), diff --git a/api/packages/game_domain/test/src/models/word_test.dart b/api/packages/game_domain/test/src/models/word_test.dart index 35d870503..b42234fc5 100644 --- a/api/packages/game_domain/test/src/models/word_test.dart +++ b/api/packages/game_domain/test/src/models/word_test.dart @@ -27,6 +27,7 @@ void main() { 'clue': 'clue', 'solvedTimestamp': 0, 'mascot': 'sparky', + 'userId': null, }), ); }); diff --git a/api/routes/game/answer.dart b/api/routes/game/answer.dart index 23acad374..b9602c31d 100644 --- a/api/routes/game/answer.dart +++ b/api/routes/game/answer.dart @@ -37,6 +37,7 @@ Future _onPost(RequestContext context) async { try { final (valid, preSolved) = await crosswordRepository.answerWord( + user.id, wordId, player.mascot, answer,