Skip to content

Commit

Permalink
feat: add solve word extension and endpoint tests
Browse files Browse the repository at this point in the history
  • Loading branch information
B0berman committed Mar 27, 2024
1 parent e2b8135 commit 35b5a6e
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 2 deletions.
17 changes: 17 additions & 0 deletions api/lib/extensions/solve_word.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'package:clock/clock.dart';
import 'package:game_domain/game_domain.dart';

/// Extension on Word to copy instance with solved timestamp to now
extension SolveWord on Word {
/// Copies instance with solved timestamp to now
Word solveWord() {
return Word(
position: position,
axis: axis,
answer: answer,
clue: clue,
hints: hints,
solvedTimestamp: clock.now().millisecondsSinceEpoch,
);
}
}
10 changes: 8 additions & 2 deletions api/routes/board/sections/[sectionId]/[wordPosition].dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:io';

import 'package:api/extensions/path_param_to_position.dart';
import 'package:api/extensions/solve_word.dart';
import 'package:crossword_repository/crossword_repository.dart';
import 'package:dart_frog/dart_frog.dart';

Expand Down Expand Up @@ -55,8 +56,13 @@ Future<Response> _onPost(
final answer = json['answer'] as String;

if (answer == word.answer) {
return Response();
final solvedWord = word.solveWord();
final newSection = section.copyWith(
words: [...section.words..remove(word), solvedWord],
);
await crosswordRepository.updateSection(newSection);
return Response.json(body: {'valid': true});
}

return Response(statusCode: HttpStatus.badRequest);
return Response.json(body: {'valid': false});
}
180 changes: 180 additions & 0 deletions api/test/routes/board/sections/[sectionId]/[wordPosition]_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// ignore_for_file: prefer_const_literals_to_create_immutables
// ignore_for_file: prefer_const_constructors

import 'dart:io';

import 'package:clock/clock.dart';
import 'package:crossword_repository/crossword_repository.dart';
import 'package:dart_frog/dart_frog.dart';
import 'package:game_domain/game_domain.dart';
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';

import '../../../../../routes/board/sections/[sectionId]/[wordPosition].dart'
as route;

class _MockRequestContext extends Mock implements RequestContext {}

class _MockRequest extends Mock implements Request {}

class _MockCrosswordRepository extends Mock implements CrosswordRepository {}

void main() {
group('/board/sections/[sectionId]/[wordPosition]', () {
late RequestContext requestContext;
late Request request;
late CrosswordRepository crosswordRepository;

setUpAll(() {
registerFallbackValue(
const BoardSection(
id: '',
position: Point(0, 0),
size: 10,
words: [],
borderWords: [],
),
);
});

setUp(() {
requestContext = _MockRequestContext();
request = _MockRequest();
crosswordRepository = _MockCrosswordRepository();

when(() => requestContext.request).thenReturn(request);
when(() => requestContext.read<CrosswordRepository>())
.thenReturn(crosswordRepository);
});

const allowedMethods = [HttpMethod.post];
final notAllowedMethods = HttpMethod.values.where(
(e) => !allowedMethods.contains(e),
);
for (final method in notAllowedMethods) {
test('returns methods not allowed when method is $method', () async {
when(() => request.method).thenReturn(method);
final response = await route.onRequest(requestContext, '0,0', '0,2');

expect(response.statusCode, HttpStatus.methodNotAllowed);
});
}

group('POST', () {
setUp(() {
when(() => request.method).thenReturn(HttpMethod.post);
});

test('returns Response with valid to true if answer is correct',
() async {
final section = BoardSection(
id: '1',
position: const Point(1, 1),
size: 100,
words: [
Word(
position: const Point(1, 1),
axis: Axis.vertical,
answer: 'flutter',
clue: '',
hints: const [],
solvedTimestamp: null,
),
],
borderWords: const [],
);
final time = DateTime.now();
final clock = Clock.fixed(time);

when(() => request.json()).thenAnswer(
(_) => Future.value({'answer': 'flutter'}),
);
when(() => crosswordRepository.findSectionByPosition(1, 1)).thenAnswer(
(_) async => section.copyWith(),
);
when(
() => crosswordRepository.updateSection(any()),
).thenAnswer((_) async {});

await withClock(clock, () async {
final response = await route.onRequest(requestContext, '1,1', '1,1');

expect(response.statusCode, HttpStatus.ok);
expect(await response.json(), equals({'valid': true}));

verify(
() => crosswordRepository.updateSection(
section.copyWith(
words: [
Word(
position: const Point(1, 1),
axis: Axis.vertical,
answer: 'flutter',
clue: '',
hints: const [],
solvedTimestamp: time.millisecondsSinceEpoch,
),
],
),
),
).called(1);
});
});

test('returns Response with valid to false if answer is incorrect',
() async {
final section = BoardSection(
id: '1',
position: const Point(1, 1),
size: 100,
words: [
Word(
position: const Point(1, 1),
axis: Axis.vertical,
answer: 'flutter',
clue: '',
hints: const [],
solvedTimestamp: null,
),
],
borderWords: const [],
);
when(() => request.json()).thenAnswer(
(_) async => {'answer': 'android'},
);
when(() => crosswordRepository.findSectionByPosition(1, 1)).thenAnswer(
(_) async => section,
);

final response = await route.onRequest(requestContext, '1,1', '1,1');

expect(response.statusCode, HttpStatus.ok);
expect(await response.json(), equals({'valid': false}));
});

test('returns Response with status BadRequest if section id is invalid',
() async {
final response = await route.onRequest(requestContext, '00', '1,2');
expect(response.statusCode, HttpStatus.badRequest);
});

test(
'returns Response with status BadRequest'
' if word position is invalid', () async {
final response = await route.onRequest(requestContext, '0,0', '12');
expect(response.statusCode, HttpStatus.badRequest);
});

test(
'returns Response with status NotFound'
' if section does not exist', () async {
when(() => crosswordRepository.findSectionByPosition(1, 1)).thenAnswer(
(_) async => null,
);

final response = await route.onRequest(requestContext, '1,1', '1,2');
expect(response.statusCode, HttpStatus.notFound);
});
});
});
}

0 comments on commit 35b5a6e

Please sign in to comment.