diff --git a/lib/api_service.dart b/lib/api_service.dart index d69e6a2..176c04c 100644 --- a/lib/api_service.dart +++ b/lib/api_service.dart @@ -71,18 +71,23 @@ String baseUrl = 'http://YOUR_IP:8000'; String origin = 'http://localhost:8080'; Future> fetchTasks(String uuid, String encryptionSecret) async { - String url = - '$baseUrl/tasks?email=email&origin=$origin&UUID=$uuid&encryptionSecret=$encryptionSecret'; - - var response = await http.get(Uri.parse(url), headers: { - "Content-Type": "application/json", - }).timeout(const Duration(seconds: 10000)); - if (response.statusCode == 200) { - List allTasks = jsonDecode(response.body); - debugPrint(allTasks.toString()); - return allTasks.map((task) => Tasks.fromJson(task)).toList(); - } else { - throw Exception('Failed to load tasks'); + try { + String url = + '$baseUrl/tasks?email=email&origin=$origin&UUID=$uuid&encryptionSecret=$encryptionSecret'; + + var response = await http.get(Uri.parse(url), headers: { + "Content-Type": "application/json", + }).timeout(const Duration(seconds: 10000)); + if (response.statusCode == 200) { + List allTasks = jsonDecode(response.body); + debugPrint(allTasks.toString()); + return allTasks.map((task) => Tasks.fromJson(task)).toList(); + } else { + throw Exception('Failed to load tasks'); + } + } catch (e) { + debugPrint('Error fetching tasks: $e'); + return []; } } diff --git a/pubspec.yaml b/pubspec.yaml index 76c5869..19db109 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -69,6 +69,8 @@ dev_dependencies: build_runner: null flutter_gen_runner: null flutter_lints: 4.0.0 + http_mock_adapter: ^0.3.0 + sqflite_common_ffi: ^2.0.0 flutter_gen: output: lib/app/utils/gen/ diff --git a/test/api_service_test.dart b/test/api_service_test.dart new file mode 100644 index 0000000..ba252fa --- /dev/null +++ b/test/api_service_test.dart @@ -0,0 +1,164 @@ +import 'dart:convert'; + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:http/http.dart' as http; +import 'package:sqflite_common_ffi/sqflite_ffi.dart'; +import 'package:taskwarrior/api_service.dart'; +import 'package:taskwarrior/app/utils/taskchampion/credentials_storage.dart'; + +import 'api_service_test.mocks.dart'; + +class MockCredentialsStorage extends Mock implements CredentialsStorage {} + +class MockMethodChannel extends Mock implements MethodChannel {} + +@GenerateMocks([MockMethodChannel, http.Client]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + databaseFactory = databaseFactoryFfi; + MockClient mockClient = MockClient(); + + setUpAll(() { + sqfliteFfiInit(); + }); + + group('Tasks model', () { + test('fromJson creates Tasks object', () { + final json = { + 'id': 1, + 'description': 'Task 1', + 'project': 'Project 1', + 'status': 'pending', + 'uuid': '123', + 'urgency': 5.0, + 'priority': 'H', + 'due': '2024-12-31', + 'end': null, + 'entry': '2024-01-01', + 'modified': '2024-11-01', + }; + + final task = Tasks.fromJson(json); + + expect(task.id, 1); + expect(task.description, 'Task 1'); + expect(task.project, 'Project 1'); + expect(task.status, 'pending'); + expect(task.uuid, '123'); + expect(task.urgency, 5.0); + expect(task.priority, 'H'); + expect(task.due, '2024-12-31'); + expect(task.entry, '2024-01-01'); + expect(task.modified, '2024-11-01'); + }); + + test('toJson converts Tasks object to JSON', () { + final task = Tasks( + id: 1, + description: 'Task 1', + project: 'Project 1', + status: 'pending', + uuid: '123', + urgency: 5.0, + priority: 'H', + due: '2024-12-31', + end: null, + entry: '2024-01-01', + modified: '2024-11-01', + ); + + final json = task.toJson(); + + expect(json['id'], 1); + expect(json['description'], 'Task 1'); + expect(json['project'], 'Project 1'); + expect(json['status'], 'pending'); + expect(json['uuid'], '123'); + expect(json['urgency'], 5.0); + expect(json['priority'], 'H'); + expect(json['due'], '2024-12-31'); + }); + }); + + group('fetchTasks', () { + test('Fetch data successfully', () async { + final responseJson = jsonEncode({'data': 'Mock data'}); + when(mockClient.get( + Uri.parse( + '$baseUrl/tasks?email=email&origin=$origin&UUID=123&encryptionSecret=secret'), + headers: { + "Content-Type": "application/json", + })).thenAnswer((_) async => http.Response(responseJson, 200)); + + final result = await fetchTasks('123', 'secret'); + + expect(result, isA>()); + }); + + test('fetchTasks returns empty array', () async { + const uuid = '123'; + const encryptionSecret = 'secret'; + + expect(await fetchTasks(uuid, encryptionSecret), isEmpty); + }); + }); + + group('TaskDatabase', () { + late TaskDatabase taskDatabase; + + setUp(() async { + taskDatabase = TaskDatabase(); + await taskDatabase.open(); + }); + + test('insertTask adds a task to the database', () async { + final task = Tasks( + id: 1, + description: 'Task 1', + project: 'Project 1', + status: 'pending', + uuid: '123', + urgency: 5.0, + priority: 'H', + due: '2024-12-31', + end: null, + entry: '2024-01-01', + modified: '2024-11-01', + ); + + await taskDatabase.insertTask(task); + + final tasks = await taskDatabase.fetchTasksFromDatabase(); + + expect(tasks.length, 1); + expect(tasks[0].description, 'Task 1'); + }); + + test('deleteAllTasksInDB removes all tasks', () async { + final task = Tasks( + id: 1, + description: 'Task 1', + project: 'Project 1', + status: 'pending', + uuid: '123', + urgency: 5.0, + priority: 'H', + due: '2024-12-31', + end: null, + entry: '2024-01-01', + modified: '2024-11-01', + ); + + await taskDatabase.insertTask(task); + await taskDatabase.deleteAllTasksInDB(); + + final tasks = await taskDatabase.fetchTasksFromDatabase(); + + expect(tasks.length, 0); + }); + }); +} diff --git a/test/api_service_test.mocks.dart b/test/api_service_test.mocks.dart new file mode 100644 index 0000000..c23b510 --- /dev/null +++ b/test/api_service_test.mocks.dart @@ -0,0 +1,401 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in taskwarrior/test/api_service_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i6; +import 'dart:convert' as _i7; +import 'dart:typed_data' as _i8; + +import 'package:flutter/services.dart' as _i2; +import 'package:http/http.dart' as _i3; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i5; + +import 'api_service_test.dart' as _i4; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeMethodCodec_0 extends _i1.SmartFake implements _i2.MethodCodec { + _FakeMethodCodec_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeBinaryMessenger_1 extends _i1.SmartFake + implements _i2.BinaryMessenger { + _FakeBinaryMessenger_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeResponse_2 extends _i1.SmartFake implements _i3.Response { + _FakeResponse_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeStreamedResponse_3 extends _i1.SmartFake + implements _i3.StreamedResponse { + _FakeStreamedResponse_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [MockMethodChannel]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMockMethodChannel extends _i1.Mock implements _i4.MockMethodChannel { + MockMockMethodChannel() { + _i1.throwOnMissingStub(this); + } + + @override + String get name => (super.noSuchMethod( + Invocation.getter(#name), + returnValue: _i5.dummyValue( + this, + Invocation.getter(#name), + ), + ) as String); + + @override + _i2.MethodCodec get codec => (super.noSuchMethod( + Invocation.getter(#codec), + returnValue: _FakeMethodCodec_0( + this, + Invocation.getter(#codec), + ), + ) as _i2.MethodCodec); + + @override + _i2.BinaryMessenger get binaryMessenger => (super.noSuchMethod( + Invocation.getter(#binaryMessenger), + returnValue: _FakeBinaryMessenger_1( + this, + Invocation.getter(#binaryMessenger), + ), + ) as _i2.BinaryMessenger); + + @override + _i6.Future invokeMethod( + String? method, [ + dynamic arguments, + ]) => + (super.noSuchMethod( + Invocation.method( + #invokeMethod, + [ + method, + arguments, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future?> invokeListMethod( + String? method, [ + dynamic arguments, + ]) => + (super.noSuchMethod( + Invocation.method( + #invokeListMethod, + [ + method, + arguments, + ], + ), + returnValue: _i6.Future?>.value(), + ) as _i6.Future?>); + + @override + _i6.Future?> invokeMapMethod( + String? method, [ + dynamic arguments, + ]) => + (super.noSuchMethod( + Invocation.method( + #invokeMapMethod, + [ + method, + arguments, + ], + ), + returnValue: _i6.Future?>.value(), + ) as _i6.Future?>); + + @override + void setMethodCallHandler( + _i6.Future Function(_i2.MethodCall)? handler) => + super.noSuchMethod( + Invocation.method( + #setMethodCallHandler, + [handler], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [Client]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockClient extends _i1.Mock implements _i3.Client { + MockClient() { + _i1.throwOnMissingStub(this); + } + + @override + _i6.Future<_i3.Response> head( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #head, + [url], + {#headers: headers}, + ), + returnValue: _i6.Future<_i3.Response>.value(_FakeResponse_2( + this, + Invocation.method( + #head, + [url], + {#headers: headers}, + ), + )), + ) as _i6.Future<_i3.Response>); + + @override + _i6.Future<_i3.Response> get( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + returnValue: _i6.Future<_i3.Response>.value(_FakeResponse_2( + this, + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + )), + ) as _i6.Future<_i3.Response>); + + @override + _i6.Future<_i3.Response> post( + Uri? url, { + Map? headers, + Object? body, + _i7.Encoding? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i6.Future<_i3.Response>.value(_FakeResponse_2( + this, + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i6.Future<_i3.Response>); + + @override + _i6.Future<_i3.Response> put( + Uri? url, { + Map? headers, + Object? body, + _i7.Encoding? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #put, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i6.Future<_i3.Response>.value(_FakeResponse_2( + this, + Invocation.method( + #put, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i6.Future<_i3.Response>); + + @override + _i6.Future<_i3.Response> patch( + Uri? url, { + Map? headers, + Object? body, + _i7.Encoding? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #patch, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i6.Future<_i3.Response>.value(_FakeResponse_2( + this, + Invocation.method( + #patch, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i6.Future<_i3.Response>); + + @override + _i6.Future<_i3.Response> delete( + Uri? url, { + Map? headers, + Object? body, + _i7.Encoding? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #delete, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i6.Future<_i3.Response>.value(_FakeResponse_2( + this, + Invocation.method( + #delete, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i6.Future<_i3.Response>); + + @override + _i6.Future read( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #read, + [url], + {#headers: headers}, + ), + returnValue: _i6.Future.value(_i5.dummyValue( + this, + Invocation.method( + #read, + [url], + {#headers: headers}, + ), + )), + ) as _i6.Future); + + @override + _i6.Future<_i8.Uint8List> readBytes( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #readBytes, + [url], + {#headers: headers}, + ), + returnValue: _i6.Future<_i8.Uint8List>.value(_i8.Uint8List(0)), + ) as _i6.Future<_i8.Uint8List>); + + @override + _i6.Future<_i3.StreamedResponse> send(_i3.BaseRequest? request) => + (super.noSuchMethod( + Invocation.method( + #send, + [request], + ), + returnValue: + _i6.Future<_i3.StreamedResponse>.value(_FakeStreamedResponse_3( + this, + Invocation.method( + #send, + [request], + ), + )), + ) as _i6.Future<_i3.StreamedResponse>); + + @override + void close() => super.noSuchMethod( + Invocation.method( + #close, + [], + ), + returnValueForMissingStub: null, + ); +} diff --git a/test/main_test.dart b/test/main_test.dart new file mode 100644 index 0000000..00fb9c1 --- /dev/null +++ b/test/main_test.dart @@ -0,0 +1,31 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:get/get.dart'; +import 'package:taskwarrior/app/routes/app_pages.dart'; + +void main() { + group('Main App Initialization', () { + testWidgets('App initializes with the correct route', + (WidgetTester tester) async { + await tester.pumpWidget(GetMaterialApp( + title: "Application", + initialRoute: AppPages.INITIAL, + getPages: AppPages.routes, + )); + + // Check if the app starts at the correct initial route + expect(Get.currentRoute, equals(AppPages.INITIAL)); + }); + + testWidgets('Splash screen displays the correct content', + (WidgetTester tester) async { + await tester.pumpWidget(GetMaterialApp( + title: "Application", + initialRoute: AppPages.INITIAL, + getPages: AppPages.routes, + )); + + // Check if the splash screen contains the expected text + expect(find.text("Setting up the app..."), findsOneWidget); + }); + }); +}