Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds reset api call #319

Merged
merged 6 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions api/routes/game/reset_streak.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'dart:async';
import 'dart:io';

import 'package:dart_frog/dart_frog.dart';
import 'package:jwt_middleware/jwt_middleware.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:logging/logging.dart';

FutureOr<Response> onRequest(RequestContext context) async {
if (context.request.method == HttpMethod.post) {
return _onPost(context);
}
return Response(statusCode: HttpStatus.methodNotAllowed);
}

Future<Response> _onPost(RequestContext context) async {
final leaderboardRepository = context.read<LeaderboardRepository>();

final user = context.read<AuthenticatedUser>();

try {
await leaderboardRepository.resetStreak(user.id);
} catch (e, s) {
context.read<Logger>().severe('Error resetting the streak', e, s);
rethrow;
}

return Response();
}
95 changes: 95 additions & 0 deletions api/test/routes/game/reset_streak_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// ignore_for_file: prefer_const_constructors

import 'dart:io';

import 'package:dart_frog/dart_frog.dart';
import 'package:jwt_middleware/jwt_middleware.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:logging/logging.dart';
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';

import '../../../routes/game/reset_streak.dart' as route;

class _MockLeaderboardRepository extends Mock
implements LeaderboardRepository {}

class _MockRequest extends Mock implements Request {}

class _MockRequestContext extends Mock implements RequestContext {}

class _MockLogger extends Mock implements Logger {}

void main() {
late LeaderboardRepository leaderboardRepository;
late Request request;
late RequestContext context;
late Logger logger;

setUp(() {
leaderboardRepository = _MockLeaderboardRepository();
request = _MockRequest();
logger = _MockLogger();

context = _MockRequestContext();
when(() => context.request).thenReturn(request);
when(() => context.read<LeaderboardRepository>())
.thenReturn(leaderboardRepository);
when(() => context.read<Logger>()).thenReturn(logger);
when(() => context.read<AuthenticatedUser>())
.thenReturn(AuthenticatedUser('id'));
});

for (final method in HttpMethod.values.toList()..remove(HttpMethod.post)) {
test(
'responds with a ${HttpStatus.methodNotAllowed} status with $method',
() async {
when(() => request.method).thenReturn(method);
when(() => leaderboardRepository.resetStreak('id'))
.thenAnswer((_) async {});

final response = await route.onRequest(context);
expect(response.statusCode, equals(HttpStatus.methodNotAllowed));
},
);
}

group('POST', () {
test(
'responds with a HttpStatus.created status when reset is correct',
() async {
when(() => request.method).thenReturn(HttpMethod.post);
when(() => leaderboardRepository.resetStreak('id'))
.thenAnswer((_) async {});

final response = await route.onRequest(context);
expect(response.statusCode, equals(HttpStatus.ok));
},
);

test(
'calls resetStreak with the user id',
() async {
when(() => request.method).thenReturn(HttpMethod.post);
when(() => leaderboardRepository.resetStreak('id'))
.thenAnswer((_) async {});

await route.onRequest(context);

verify(() => leaderboardRepository.resetStreak('id')).called(1);
},
);

test(
'throws error when resetStreak throws exception',
() async {
when(() => request.method).thenReturn(HttpMethod.post);
when(() => leaderboardRepository.resetStreak('id'))
.thenThrow(Exception());

final response = route.onRequest(context);
expect(response, throwsA(isA<Exception>()));
},
);
});
}
16 changes: 16 additions & 0 deletions packages/api_client/lib/src/resources/leaderboard_resource.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,20 @@ class LeaderboardResource {
);
}
}

/// Post /game/reset_streak
Future<void> resetStreak() async {
final response = await _apiClient.post(
'/game/reset_streak',
);

if (response.statusCode != HttpStatus.ok) {
throw ApiClientError(
'POST /game/reset_streak returned status '
'${response.statusCode} with the following response: '
'"${response.body}"',
StackTrace.current,
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,5 +132,42 @@ void main() {
);
});
});

group('resetStreak', () {
setUp(() {
when(() => apiClient.post(any())).thenAnswer((_) async => response);
});

test('makes the correct call', () async {
when(() => response.statusCode).thenReturn(HttpStatus.ok);
await resource.resetStreak();

verify(
() => apiClient.post(
'/game/reset_streak',
),
).called(1);
});

test('throws ApiClientError when request fails', () async {
when(() => response.statusCode)
.thenReturn(HttpStatus.internalServerError);
when(() => response.body).thenReturn('Oops');

await expectLater(
() => resource.resetStreak(),
throwsA(
isA<ApiClientError>().having(
(e) => e.cause,
'cause',
equals(
'POST /game/reset_streak returned status 500 '
'with the following response: "Oops"',
),
),
),
);
});
});
});
}
Loading