Skip to content

Commit

Permalink
Merge pull request #82 from chornerman/feature/#15-backend-logout
Browse files Browse the repository at this point in the history
[#15] Implement backend for logout functionality
  • Loading branch information
chornerman committed Dec 27, 2022
2 parents a1a30e2 + 084b4a8 commit ee5f58c
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 0 deletions.
20 changes: 20 additions & 0 deletions lib/api/repository/auth_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:injectable/injectable.dart';
import 'package:survey/api/exception/network_exceptions.dart';
import 'package:survey/api/grant_type.dart';
import 'package:survey/api/request/login_request.dart';
import 'package:survey/api/request/logout_request.dart';
import 'package:survey/api/request/reset_password_request.dart';
import 'package:survey/api/service/auth_service.dart';
import 'package:survey/env_variables.dart';
Expand All @@ -13,6 +14,10 @@ abstract class AuthRepository {
required String password,
});

Future<void> logout({
required String token,
});

Future<void> resetPassword({
required String email,
});
Expand Down Expand Up @@ -45,6 +50,21 @@ class AuthRepositoryImpl extends AuthRepository {
}
}

@override
Future<void> logout({required String token}) async {
try {
await _authService.logout(
LogoutRequest(
token: token,
clientId: EnvVariables.clientId,
clientSecret: EnvVariables.clientSecret,
),
);
} catch (exception) {
throw NetworkExceptions.fromDioException(exception);
}
}

@override
Future<void> resetPassword({required String email}) async {
try {
Expand Down
21 changes: 21 additions & 0 deletions lib/api/request/logout_request.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:json_annotation/json_annotation.dart';

part 'logout_request.g.dart';

@JsonSerializable()
class LogoutRequest {
final String token;
final String clientId;
final String clientSecret;

LogoutRequest({
required this.token,
required this.clientId,
required this.clientSecret,
});

factory LogoutRequest.fromJson(Map<String, dynamic> json) =>
_$LogoutRequestFromJson(json);

Map<String, dynamic> toJson() => _$LogoutRequestToJson(this);
}
6 changes: 6 additions & 0 deletions lib/api/service/auth_service.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:dio/dio.dart';
import 'package:retrofit/retrofit.dart';
import 'package:survey/api/request/login_request.dart';
import 'package:survey/api/request/logout_request.dart';
import 'package:survey/api/request/reset_password_request.dart';
import 'package:survey/api/response/login_response.dart';

Expand All @@ -15,6 +16,11 @@ abstract class AuthService {
@Body() LoginRequest body,
);

@POST('/api/v1/oauth/revoke')
Future<void> logout(
@Body() LogoutRequest body,
);

@POST('/api/v1/passwords')
Future<void> resetPassword(
@Body() ResetPasswordRequest body,
Expand Down
7 changes: 7 additions & 0 deletions lib/database/shared_preferences_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ abstract class SharedPreferencesUtils {
void saveTokenType(String tokenType);

void saveRefreshToken(String refreshToken);

void clear();
}

@Singleton(as: SharedPreferencesUtils)
Expand Down Expand Up @@ -54,4 +56,9 @@ class SharedPreferencesUtilsImpl extends SharedPreferencesUtils {
void saveRefreshToken(String refreshToken) async {
await _sharedPreferences.setString(_refreshTokenKey, refreshToken);
}

@override
void clear() async {
await _sharedPreferences.clear();
}
}
37 changes: 37 additions & 0 deletions lib/usecase/logout_use_case.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'dart:async';

import 'package:injectable/injectable.dart';
import 'package:survey/api/exception/network_exceptions.dart';
import 'package:survey/api/repository/auth_repository.dart';
import 'package:survey/database/hive_utils.dart';
import 'package:survey/database/shared_preferences_utils.dart';
import 'package:survey/usecase/base/base_use_case.dart';

@Injectable()
class LogoutUseCase extends NoInputUseCase<void> {
final AuthRepository _repository;
final SharedPreferencesUtils _sharedPreferencesUtils;
final HiveUtils _hiveUtils;

const LogoutUseCase(
this._repository,
this._sharedPreferencesUtils,
this._hiveUtils,
);

@override
Future<Result<void>> call() async {
final token = await _sharedPreferencesUtils.accessToken;
return _repository
.logout(token: token)
.then((value) => _clearTokensAndCache())
.onError<NetworkExceptions>(
(exception, stackTrace) => Failed(UseCaseException(exception)));
}

Result<void> _clearTokensAndCache() {
_sharedPreferencesUtils.clear();
_hiveUtils.clearSurveys();
return Success(null);
}
}
15 changes: 15 additions & 0 deletions test/api/repository/auth_repository_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,21 @@ void main() {
expect(result, throwsA(isA<NetworkExceptions>()));
});

test('When calling logout successfully, it returns empty result', () async {
when(mockAuthService.logout(any)).thenAnswer((_) async => null);

await repository.logout(token: 'token');
});

test('When calling logout failed, it returns NetworkExceptions error',
() async {
when(mockAuthService.logout(any)).thenThrow(MockDioError());

result() => repository.logout(token: 'token');

expect(result, throwsA(isA<NetworkExceptions>()));
});

test('When calling reset password successfully, it returns empty result',
() async {
when(mockAuthService.resetPassword(any)).thenAnswer((_) async => null);
Expand Down
53 changes: 53 additions & 0 deletions test/usecase/logout_use_case_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:survey/api/exception/network_exceptions.dart';
import 'package:survey/usecase/base/base_use_case.dart';
import 'package:survey/usecase/logout_use_case.dart';

import '../../test/mock/mock_dependencies.mocks.dart';

void main() {
group('LogoutUseCaseTest', () {
late MockAuthRepository mockRepository;
late MockSharedPreferencesUtils mockSharedPreferencesUtils;
late MockHiveUtils mockHiveUtils;
late LogoutUseCase useCase;

setUp(() {
mockRepository = MockAuthRepository();
mockSharedPreferencesUtils = MockSharedPreferencesUtils();
mockHiveUtils = MockHiveUtils();
useCase = LogoutUseCase(
mockRepository,
mockSharedPreferencesUtils,
mockHiveUtils,
);
});

test(
'When executing use case and repository returns success, it returns Success result',
() async {
when(mockRepository.logout(token: anyNamed('token')))
.thenAnswer((_) async => null);

final result = await useCase.call();

expect(result, isA<Success>());
verify(mockSharedPreferencesUtils.clear()).called(1);
verify(mockHiveUtils.clearSurveys()).called(1);
});

test(
'When executing use case and repository returns error, it returns Failed result',
() async {
final exception = NetworkExceptions.badRequest();
when(mockRepository.logout(token: anyNamed('token')))
.thenAnswer((_) => Future.error(exception));

final result = await useCase.call();

expect(result, isA<Failed>());
expect((result as Failed).exception.actualException, exception);
});
});
}

0 comments on commit ee5f58c

Please sign in to comment.