From e3d2cbb7a6def5491890f361d8db5486c6fb7b87 Mon Sep 17 00:00:00 2001 From: akhdanre Date: Wed, 21 May 2025 22:29:22 +0700 Subject: [PATCH] fix: adjustment on the notification and loading --- android/app/build.gradle | 13 ++ lib/core/endpoint/api_endpoint.dart | 4 +- lib/data/services/answer_service.dart | 8 +- lib/data/services/quiz_service.dart | 16 +-- .../login/controllers/login_controller.dart | 3 +- .../controller/register_controller.dart | 6 + test/service/answer_service_test.dart | 128 ++++++++++++++++++ test/service/quiz_service_test.dart | 114 ++++++++++++++++ 8 files changed, 277 insertions(+), 15 deletions(-) create mode 100644 test/service/answer_service_test.dart create mode 100644 test/service/quiz_service_test.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index c913db7..2cfd4a7 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -38,6 +38,15 @@ android { storePassword = "uppercase12" } + + release { + keyAlias = "genso-prod" + keyPassword = "oukenzeumasio" + storeFile = file("my-release-key.jks") + storePassword = "oukenzeumasio" + + } + } buildTypes { @@ -46,6 +55,10 @@ android { // Signing with the debug keys for now, so `flutter run --release` works. signingConfig = signingConfigs.debug } + + release { + signingConfig = signingConfigs.release + } } } diff --git a/lib/core/endpoint/api_endpoint.dart b/lib/core/endpoint/api_endpoint.dart index b62104f..0aa29d4 100644 --- a/lib/core/endpoint/api_endpoint.dart +++ b/lib/core/endpoint/api_endpoint.dart @@ -1,6 +1,6 @@ class APIEndpoint { - static const String baseUrl = "http://192.168.1.9:5000"; - // static const String baseUrl = "http://172.16.106.133:5000"; + // static const String baseUrl = "http://192.168.1.9:5000"; + static const String baseUrl = "http://103.193.178.121:5000"; static const String api = "$baseUrl/api"; static const String login = "/login"; diff --git a/lib/data/services/answer_service.dart b/lib/data/services/answer_service.dart index 7810f17..79b344a 100644 --- a/lib/data/services/answer_service.dart +++ b/lib/data/services/answer_service.dart @@ -7,17 +7,17 @@ import 'package:quiz_app/data/models/history/participant_history_result.dart'; import 'package:quiz_app/data/providers/dio_client.dart'; class AnswerService extends GetxService { - late final Dio _dio; + late final Dio dio; @override void onInit() { - _dio = Get.find().dio; + dio = Get.find().dio; super.onInit(); } Future submitQuizAnswers(Map payload) async { try { - await _dio.post( + await dio.post( APIEndpoint.quizAnswer, data: payload, ); @@ -30,7 +30,7 @@ class AnswerService extends GetxService { Future?> getAnswerSession(String sessionId, String userId) async { try { - final response = await _dio.post(APIEndpoint.quizAnswerSession, data: { + final response = await dio.post(APIEndpoint.quizAnswerSession, data: { "session_id": sessionId, "user_id": userId, }); diff --git a/lib/data/services/quiz_service.dart b/lib/data/services/quiz_service.dart index c1146fe..80cfa4e 100644 --- a/lib/data/services/quiz_service.dart +++ b/lib/data/services/quiz_service.dart @@ -9,17 +9,17 @@ import 'package:quiz_app/data/models/quiz/quiz_listing_model.dart'; import 'package:quiz_app/data/providers/dio_client.dart'; class QuizService extends GetxService { - late final Dio _dio; + late final Dio dio; @override void onInit() { - _dio = Get.find().dio; + dio = Get.find().dio; super.onInit(); } Future createQuiz(QuizCreateRequestModel request) async { try { - final response = await _dio.post( + final response = await dio.post( APIEndpoint.quiz, data: request.toJson(), ); @@ -37,7 +37,7 @@ class QuizService extends GetxService { Future>> createQuizAuto(String sentence) async { try { - final response = await _dio.post( + final response = await dio.post( APIEndpoint.quizGenerate, data: {"sentence": sentence}, ); @@ -63,7 +63,7 @@ class QuizService extends GetxService { Future>?> userQuiz(String userId, int page) async { try { - final response = await _dio.get("${APIEndpoint.userQuiz}/$userId?page=$page"); + final response = await dio.get("${APIEndpoint.userQuiz}/$userId?page=$page"); if (response.statusCode == 200) { final parsedResponse = BaseResponseModel>.fromJson( response.data, @@ -82,7 +82,7 @@ class QuizService extends GetxService { Future>?> recomendationQuiz({int page = 1, int amount = 3}) async { try { - final response = await _dio.get("${APIEndpoint.quizRecomendation}?page=$page&limit=$amount"); + final response = await dio.get("${APIEndpoint.quizRecomendation}?page=$page&limit=$amount"); if (response.statusCode == 200) { final parsedResponse = BaseResponseModel>.fromJson( @@ -110,7 +110,7 @@ class QuizService extends GetxService { }; final uri = Uri.parse(APIEndpoint.quizSearch).replace(queryParameters: queryParams); - final response = await _dio.getUri(uri); + final response = await dio.getUri(uri); if (response.statusCode == 200) { final parsedResponse = BaseResponseModel>.fromJson( @@ -130,7 +130,7 @@ class QuizService extends GetxService { Future?> getQuizById(String quizId) async { try { - final response = await _dio.get("${APIEndpoint.quiz}/$quizId"); + final response = await dio.get("${APIEndpoint.quiz}/$quizId"); if (response.statusCode == 200) { final parsedResponse = BaseResponseModel.fromJson( diff --git a/lib/feature/login/controllers/login_controller.dart b/lib/feature/login/controllers/login_controller.dart index 7b4da7f..e512f05 100644 --- a/lib/feature/login/controllers/login_controller.dart +++ b/lib/feature/login/controllers/login_controller.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:quiz_app/app/routes/app_pages.dart'; import 'package:quiz_app/component/global_button.dart'; +import 'package:quiz_app/core/utils/custom_notification.dart'; import 'package:quiz_app/core/utils/logger.dart'; import 'package:quiz_app/data/controllers/user_controller.dart'; import 'package:quiz_app/data/entity/user/user_entity.dart'; @@ -74,7 +75,7 @@ class LoginController extends GetxController { Get.offAllNamed(AppRoutes.mainPage); } catch (e, stackTrace) { logC.e(e, stackTrace: stackTrace); - Get.snackbar("Error", "Failed to connect to server"); + CustomNotification.error(title: "failed", message: "Check username and password"); } finally { isLoading.value = false; } diff --git a/lib/feature/register/controller/register_controller.dart b/lib/feature/register/controller/register_controller.dart index 5428b9d..010cec3 100644 --- a/lib/feature/register/controller/register_controller.dart +++ b/lib/feature/register/controller/register_controller.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:quiz_app/core/utils/custom_floating_loading.dart'; +import 'package:quiz_app/core/utils/custom_notification.dart'; import 'package:quiz_app/data/models/register/register_request.dart'; import 'package:quiz_app/data/services/auth_service.dart'; @@ -59,6 +61,7 @@ class RegisterController extends GetxController { } try { + CustomFloatingLoading.showLoadingDialog(Get.context!); await _authService.register( RegisterRequestModel( email: email, @@ -68,7 +71,10 @@ class RegisterController extends GetxController { phone: phone, ), ); + Get.back(); + CustomFloatingLoading.hideLoadingDialog(Get.context!); + CustomNotification.success(title: "register success", message: "created account successfuly"); } catch (e) { Get.snackbar("Error", "Failed to register: ${e.toString()}"); } diff --git a/test/service/answer_service_test.dart b/test/service/answer_service_test.dart new file mode 100644 index 0000000..da20384 --- /dev/null +++ b/test/service/answer_service_test.dart @@ -0,0 +1,128 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:dio/dio.dart'; +import 'package:quiz_app/core/endpoint/api_endpoint.dart'; +import 'package:quiz_app/data/models/base/base_model.dart'; +import 'package:quiz_app/data/services/answer_service.dart'; + +class MockDio extends Mock implements Dio {} + +void main() { + late MockDio mockDio; + late AnswerService answerService; + + setUp(() { + mockDio = MockDio(); + answerService = AnswerService(); + answerService.dio = mockDio; + }); + + group('AnswerService Tests', () { + test('submitQuizAnswers - Success', () async { + final payload = {'question_id': 'q1', 'answer': 'A'}; + + when(() => mockDio.post(APIEndpoint.quizAnswer, data: payload)).thenAnswer((_) async => Response( + statusCode: 200, + data: {}, + requestOptions: RequestOptions(path: APIEndpoint.quizAnswer), + )); + + final result = await answerService.submitQuizAnswers(payload); + + expect(result, isA()); + expect(result?.message, 'success'); + + verify(() => mockDio.post(APIEndpoint.quizAnswer, data: payload)).called(1); + }); + + test('submitQuizAnswers - Failure', () async { + final payload = {'question_id': 'q1', 'answer': 'A'}; + + when(() => mockDio.post(APIEndpoint.quizAnswer, data: payload)).thenThrow(DioException( + requestOptions: RequestOptions(path: APIEndpoint.quizAnswer), + error: 'Network Error', + type: DioExceptionType.connectionError, + )); + + final result = await answerService.submitQuizAnswers(payload); + + expect(result, isNull); + verify(() => mockDio.post(APIEndpoint.quizAnswer, data: payload)).called(1); + }); + + test('getAnswerSession - Success', () async { + final sessionId = '682a26b3bedac6c20a215452'; + final userId = '680f0e63180b5c19b3751d42'; + final responseData = { + "message": "Successfully retrieved the answer", + "data": { + "id": "682a26e6bedac6c20a215453", + "session_id": "682a26b3bedac6c20a215452", + "quiz_id": "682a120f18339f4cc31318e4", + "user_id": "680f0e63180b5c19b3751d42", + "answered_at": "2025-05-19 01:28:22", + "answers": [ + { + "index": 1, + "question": "Siapakah ketua Wali Songo yang juga dikenal sebagai Sunan Gresik?", + "target_answer": "Maulana Malik Ibrahim", + "duration": 30, + "type": "fill_the_blank", + "options": null, + "answer": "maulana Malik ibrahim", + "is_correct": true, + "time_spent": 8.0 + } + ], + "total_score": 100, + "total_correct": 1 + }, + "meta": null + }; + + when(() => mockDio.post(APIEndpoint.quizAnswerSession, data: { + "session_id": sessionId, + "user_id": userId, + })).thenAnswer((_) async => Response( + statusCode: 200, + data: responseData, + requestOptions: RequestOptions(path: APIEndpoint.quizAnswerSession), + )); + + final result = await answerService.getAnswerSession(sessionId, userId); + + expect(result, isNotNull); + expect(result?.data?.sessionId, sessionId); + expect(result?.data?.userId, userId); + expect(result?.data?.totalScore, 100); + + verify(() => mockDio.post(APIEndpoint.quizAnswerSession, data: { + "session_id": sessionId, + "user_id": userId, + })).called(1); + }); + + test('getAnswerSession - Failure', () async { + final sessionId = ''; + final userId = ''; + + when(() => mockDio.post(APIEndpoint.quizAnswerSession, data: { + "session_id": sessionId, + "user_id": userId, + })).thenThrow(DioException( + requestOptions: RequestOptions(path: APIEndpoint.quizAnswerSession), + error: 'Network Error', + type: DioExceptionType.connectionError, + )); + + final result = await answerService.getAnswerSession(sessionId, userId); + + expect(result, isNull); + + verify(() => mockDio.post(APIEndpoint.quizAnswerSession, data: { + "session_id": sessionId, + "user_id": userId, + })).called(1); + }); + }); +} diff --git a/test/service/quiz_service_test.dart b/test/service/quiz_service_test.dart new file mode 100644 index 0000000..139bbd6 --- /dev/null +++ b/test/service/quiz_service_test.dart @@ -0,0 +1,114 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:dio/dio.dart'; +import 'package:quiz_app/data/models/quiz/question_listings_model.dart'; +import 'package:quiz_app/data/providers/dio_client.dart'; +import 'package:quiz_app/data/services/quiz_service.dart'; +import 'package:quiz_app/data/models/quiz/question_create_request.dart'; + +class MockDio extends Mock implements Dio {} + +class MockApiClient extends Mock implements ApiClient {} + +void main() { + late MockDio mockDio; + late QuizService quizService; + + setUp(() { + mockDio = MockDio(); + quizService = QuizService(); + quizService.dio = mockDio; + }); + + group('createQuiz', () { + final request = QuizCreateRequestModel( + title: 'Test Quiz', + description: 'A sample quiz description', + isPublic: true, + date: '2025-05-19', + totalQuiz: 1, + limitDuration: 60, + authorId: 'author_123', + subjectId: 'subject_456', + questionListings: [ + QuestionListing( + index: 1, + question: 'Sample question?', + targetAnswer: 'Sample Answer', + duration: 30, + type: 'multiple_choice', + ) + ], + ); + + test('returns true when status code is 201', () async { + when(() => mockDio.post(any(), data: any(named: 'data'))).thenAnswer( + (_) async => Response( + requestOptions: RequestOptions(path: ''), + statusCode: 201, + ), + ); + + final result = await quizService.createQuiz(request); + expect(result, true); + }); + + test('throws Exception on non-201 response', () async { + when(() => mockDio.post(any(), data: any(named: 'data'))).thenAnswer( + (_) async => Response( + requestOptions: RequestOptions(path: ''), + statusCode: 400, + ), + ); + + expect(() => quizService.createQuiz(request), throwsException); + }); + + test('throws Exception on Dio error', () async { + when(() => mockDio.post(any(), data: any(named: 'data'))).thenThrow(Exception('Network Error')); + + expect(() => quizService.createQuiz(request), throwsException); + }); + }); + + group('createQuizAuto', () { + const sentence = "This is a test sentence."; + final mockResponseData = { + 'message': "succes create quiz automatic", + 'data': [ + {'qustion': 'What is this?', 'answer': 'A test.'}, + ] + }; + + test('returns BaseResponseModel when status code is 200', () async { + when(() => mockDio.post(any(), data: any(named: 'data'))).thenAnswer( + (_) async => Response( + requestOptions: RequestOptions(path: ''), + statusCode: 200, + data: mockResponseData, + ), + ); + + final result = await quizService.createQuizAuto(sentence); + expect(result.data, isA>()); + expect(result.data!.first.qustion, 'What is this?'); + }); + + test('throws Exception on non-200 response', () async { + when(() => mockDio.post(any(), data: any(named: 'data'))).thenAnswer( + (_) async => Response( + requestOptions: RequestOptions(path: ''), + statusCode: 500, + ), + ); + + expect(() => quizService.createQuizAuto(sentence), throwsException); + }); + + test('throws Exception on Dio error', () async { + when(() => mockDio.post(any(), data: any(named: 'data'))).thenThrow(Exception('Network Error')); + + expect(() => quizService.createQuizAuto(sentence), throwsException); + }); + }); +}