Skip to content

Commit

Permalink
test: fix test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
cevheri committed Jan 1, 2025
1 parent c863ec4 commit e2b50aa
Show file tree
Hide file tree
Showing 12 changed files with 696 additions and 229 deletions.
19 changes: 19 additions & 0 deletions lib/data/models/send_otp_request.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:dart_json_mapper/dart_json_mapper.dart';

@JsonSerializable()
class SendOtpRequest {
final String email;

SendOtpRequest({required this.email});

Map<String, dynamic> toJson() => {"email": email};

static SendOtpRequest? fromJson(Map<String, dynamic> json) {
try {
if (!json.containsKey('email')) return null;
return JsonMapper.fromMap<SendOtpRequest>(json);
} catch (e) {
return null;
}
}
}
20 changes: 20 additions & 0 deletions lib/data/models/verify_otp_request.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:dart_json_mapper/dart_json_mapper.dart';

@JsonSerializable()
class VerifyOtpRequest {
final String email;
final String otp;

VerifyOtpRequest({required this.email, required this.otp});

Map<String, dynamic> toJson() => {"email": email, "otp": otp};

static VerifyOtpRequest? fromJson(Map<String, dynamic> json) {
try {
if (!json.containsKey('email') || !json.containsKey('otp')) return null;
return JsonMapper.fromMap<VerifyOtpRequest>(json);
} catch (e) {
return null;
}
}
}
39 changes: 14 additions & 25 deletions lib/data/repository/login_repository.dart
Original file line number Diff line number Diff line change
@@ -1,37 +1,15 @@
import 'dart:io';

import 'package:dart_json_mapper/dart_json_mapper.dart';
import 'package:flutter_bloc_advance/configuration/app_logger.dart';
import 'package:flutter_bloc_advance/configuration/local_storage.dart';
import 'package:flutter_bloc_advance/data/app_api_exception.dart';
import 'package:flutter_bloc_advance/data/models/send_otp_request.dart';
import 'package:flutter_bloc_advance/data/models/verify_otp_request.dart';

import '../http_utils.dart';
import '../models/jwt_token.dart';
import '../models/user_jwt.dart';

@JsonSerializable()
class SendOtpRequest {
final String email;

SendOtpRequest({required this.email});

Map<String, dynamic> toJson() => {"email": email};

static SendOtpRequest? fromJson(Map<String, dynamic> json) => JsonMapper.fromMap<SendOtpRequest>(json);
}

@JsonSerializable()
class VerifyOtpRequest {
final String email;
final String otp;

VerifyOtpRequest({required this.email, required this.otp});

Map<String, dynamic> toJson() => {"email": email, "otp": otp};

static VerifyOtpRequest? fromJson(Map<String, dynamic> json) => JsonMapper.fromMap<VerifyOtpRequest>(json);
}

class LoginRepository {
static final _log = AppLogger.getLogger("LoginRepository");

Expand Down Expand Up @@ -71,9 +49,12 @@ class LoginRepository {

Future<void> sendOtp(SendOtpRequest request) async {
_log.debug("BEGIN:sendOtp repository start email: {}", [request.email]);
if (request.email.isEmpty) {
throw BadRequestException("Invalid email");
}
final headers = {"Content-Type": "application/json"};
final response = await HttpUtils.postRequest<SendOtpRequest>("/authenticate/send-otp", request, headers: headers);
if(response.statusCode >= HttpStatus.badRequest){
if (response.statusCode >= HttpStatus.badRequest) {
throw BadRequestException(response.body);
}
_log.debug("successful response: {}", [response.body]);
Expand All @@ -82,6 +63,14 @@ class LoginRepository {

Future<JWTToken?> verifyOtp(VerifyOtpRequest request) async {
_log.debug("BEGIN:verifyOtp repository start email: {}", [request.email]);
if (request.email.isEmpty || request.otp.isEmpty) {
throw BadRequestException("Invalid email or OTP");
}

if (request.otp.length != 6) {
throw BadRequestException("Invalid OTP");
}

final headers = {"Content-Type": "application/json"};
final response = await HttpUtils.postRequest<VerifyOtpRequest>("/authenticate/verify-otp", request, headers: headers);
return JWTToken.fromJsonString(response.body);
Expand Down
172 changes: 87 additions & 85 deletions lib/main/main_local.mapper.g.dart

Large diffs are not rendered by default.

172 changes: 87 additions & 85 deletions lib/main/main_prod.mapper.g.dart

Large diffs are not rendered by default.

16 changes: 11 additions & 5 deletions lib/presentation/screen/login/bloc/login_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_bloc_advance/configuration/app_logger.dart';
import 'package:flutter_bloc_advance/configuration/local_storage.dart';
import 'package:flutter_bloc_advance/data/app_api_exception.dart';
import 'package:flutter_bloc_advance/data/models/send_otp_request.dart';
import 'package:flutter_bloc_advance/data/models/verify_otp_request.dart';
import 'package:flutter_bloc_advance/data/repository/account_repository.dart';

import '../../../../data/models/user_jwt.dart';
Expand All @@ -16,9 +18,11 @@ part 'login_state.dart';
class LoginBloc extends Bloc<LoginEvent, LoginState> {
static final _log = AppLogger.getLogger("LoginBloc");
final LoginRepository _repository;
final AccountRepository _accountRepository;

LoginBloc({required LoginRepository repository})
LoginBloc({required LoginRepository repository, AccountRepository? accountRepository})
: _repository = repository,
_accountRepository = accountRepository ?? AccountRepository(),
super(const LoginState()) {
on<LoginFormSubmitted>(_onSubmit);
on<TogglePasswordVisibility>((event, emit) => emit(state.copyWith(passwordVisible: !state.passwordVisible)));
Expand All @@ -27,6 +31,8 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> {
on<VerifyOtpSubmitted>(_onVerifyOtpSubmitted);
}



@override
void onTransition(Transition<LoginEvent, LoginState> transition) {
super.onTransition(transition);
Expand All @@ -50,7 +56,7 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> {
_log.debug("onSubmit save storage token: {}", [token.idToken]);
await AppLocalStorage().save(StorageKeys.username.name, event.username);
_log.debug("onSubmit save storage username: {}", [event.username]);
final user = await AccountRepository().getAccount();
final user = await _accountRepository.getAccount();
await AppLocalStorage().save(StorageKeys.roles.name, user.authorities);
_log.debug("onSubmit save storage roles: {}", [user.authorities]);

Expand Down Expand Up @@ -82,7 +88,7 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> {
emit(LoginOtpSentState(email: event.email));
_log.debug("END: onSendOtpRequested SendOtpRequested event success: {}", [event.email]);
} catch (e) {
emit(LoginErrorState(message: "OTP gönderme hatası: ${e.toString()}"));
emit(LoginErrorState(message: "Send OTP error: ${e.toString()}"));
_log.error("END: onSendOtpRequested SendOtpRequested event error: {}", [e.toString()]);
}
}
Expand All @@ -97,15 +103,15 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> {
await AppLocalStorage().save(StorageKeys.jwtToken.name, token.idToken);
await AppLocalStorage().save(StorageKeys.username.name, event.email);

final user = await AccountRepository().getAccount();
final user = await _accountRepository.getAccount();
await AppLocalStorage().save(StorageKeys.roles.name, user.authorities);

emit(LoginLoadedState(username: event.email, password: event.otpCode));
} else {
throw BadRequestException("Invalid OTP Token");
}
} catch (e) {
emit(LoginErrorState(message: "OTP validation error: ${e.toString()}"));
emit(const LoginErrorState(message: "OTP validation error"));
}
}
}
61 changes: 61 additions & 0 deletions test/data/model/otp_send_model_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import 'package:flutter_bloc_advance/data/models/send_otp_request.dart';
import 'package:flutter_bloc_advance/main/main_local.mapper.g.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
group('SendOtpRequest', () {
test('should create SendOtpRequest instance with email', () {
// given
const testEmail = '[email protected]';

// when
final request = SendOtpRequest(email: testEmail);

// then
expect(request.email, equals(testEmail));
});

test('should convert SendOtpRequest to JSON correctly', () {
// given
const testEmail = '[email protected]';
final request = SendOtpRequest(email: testEmail);

// when
final json = request.toJson();

// then
expect(json, {
'email': '[email protected]',
});
});

test('should create SendOtpRequest from JSON correctly', () {
initializeJsonMapper();
// given
final json = {
'email': '[email protected]',
};

// when
final request = SendOtpRequest.fromJson(json);

// then
expect(request, isNotNull);
expect(request?.email, equals('[email protected]'));
});

test('should return null when fromJson is called with invalid JSON', () {
initializeJsonMapper();
// given
final invalidJson = {
'invalid_key': '[email protected]',
};

// when
final request = SendOtpRequest.fromJson(invalidJson);

// then
expect(request, isNull);
});
});
}
92 changes: 92 additions & 0 deletions test/data/model/otp_verify_model_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import 'package:flutter_bloc_advance/data/models/verify_otp_request.dart';
import 'package:flutter_bloc_advance/main/main_local.mapper.g.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
group('VerifyOtpRequest', () {
test('should create VerifyOtpRequest instance with email and otp', () {
// given
const testEmail = '[email protected]';
const testOtp = '123456';

// when
final request = VerifyOtpRequest(email: testEmail, otp: testOtp);

// then
expect(request.email, equals(testEmail));
expect(request.otp, equals(testOtp));
});

test('should convert VerifyOtpRequest to JSON correctly', () {
// given
const testEmail = '[email protected]';
const testOtp = '123456';
final request = VerifyOtpRequest(email: testEmail, otp: testOtp);

// when
final json = request.toJson();

// then
expect(json, {
'email': '[email protected]',
'otp': '123456',
});
});

test('should create VerifyOtpRequest from JSON correctly', () {
initializeJsonMapper();
// given
final json = {
'email': '[email protected]',
'otp': '123456',
};

// when
final request = VerifyOtpRequest.fromJson(json);

// then
expect(request, isNotNull);
expect(request?.email, equals('[email protected]'));
expect(request?.otp, equals('123456'));
});

test('should return null when fromJson is called with invalid JSON', () {
// given
final invalidJson = {
'invalid_key': '[email protected]',
};

// when
final request = VerifyOtpRequest.fromJson(invalidJson);

// then
expect(request, isNull);
});

test('should return null when fromJson is called with missing otp', () {
// given
final invalidJson = {
'email': '[email protected]',
};

// when
final request = VerifyOtpRequest.fromJson(invalidJson);

// then
expect(request, isNull);
});

test('should return null when fromJson is called with missing email', () {
// given
final invalidJson = {
'otp': '123456',
};

// when
final request = VerifyOtpRequest.fromJson(invalidJson);

// then
expect(request, isNull);
});
});
}
37 changes: 37 additions & 0 deletions test/data/repository/login_repository_test.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import 'package:flutter_bloc_advance/configuration/local_storage.dart';
import 'package:flutter_bloc_advance/data/app_api_exception.dart';
import 'package:flutter_bloc_advance/data/models/jwt_token.dart';
import 'package:flutter_bloc_advance/data/models/send_otp_request.dart';
import 'package:flutter_bloc_advance/data/models/user_jwt.dart';
import 'package:flutter_bloc_advance/data/models/verify_otp_request.dart';
import 'package:flutter_bloc_advance/data/repository/login_repository.dart';
import 'package:flutter_test/flutter_test.dart';

Expand Down Expand Up @@ -50,4 +53,38 @@ void main() {
expect(await AppLocalStorage().read(StorageKeys.jwtToken.name), null);
});
});

group("Login Repository sendOtp", () {
test("Given valid email when sendOtp then complete successfully", () async {
TestUtils().setupAuthentication();
final request = SendOtpRequest(email: "[email protected]");

expect(() async => await LoginRepository().sendOtp(request), returnsNormally);
});

test("Given invalid email when sendOtp then throws BadRequestException", () async {
TestUtils().setupAuthentication();
final request = SendOtpRequest(email: "");

await expectLater(LoginRepository().sendOtp(request), throwsA(isA<BadRequestException>()));
});
});

group("Login Repository verifyOtp", () {
test("Given valid OTP when verify then return JWTToken successfully", () async {
TestUtils().setupAuthentication();
final request = VerifyOtpRequest(email: "[email protected]", otp: "123456");

final result = await LoginRepository().verifyOtp(request);
expect(result, isA<JWTToken>());
expect(result?.idToken, "MOCK_TOKEN");
});

test("Given invalid OTP when verify then throws BadRequestException", () async {
TestUtils().setupAuthentication();
final request = VerifyOtpRequest(email: "[email protected]", otp: "1234567");

await expectLater(LoginRepository().verifyOtp(request), throwsA(isA<BadRequestException>()));
});
});
}
Loading

0 comments on commit e2b50aa

Please sign in to comment.