diff --git a/lib/globals.dart b/lib/globals.dart index 2aa6ca00..94eaadce 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -14,6 +14,12 @@ class AppEnvironment { supportDir = (await getApplicationSupportDirectory()).path; cameras = await availableCameras(); } + + static void initTesting() { + cacheDir = '/tmp/twonly_cache'; + supportDir = '/tmp/twonly_support'; + cameras = []; + } } class AppState { diff --git a/lib/src/visual/views/groups/group.view.dart b/lib/src/visual/views/groups/group.view.dart index 97089684..934291c2 100644 --- a/lib/src/visual/views/groups/group.view.dart +++ b/lib/src/visual/views/groups/group.view.dart @@ -140,11 +140,11 @@ class _GroupViewState extends State { final keyPair = IdentityKeyPair.fromSerialized( _group!.myGroupPrivateKey!, ); - success = await removeMemberFromGroup( + success = !(await removeMemberFromGroup( _group!, keyPair.getPublicKey().serialize(), userService.currentUser.userId, - ); + )); } else { success = await leaveAsNonAdminFromGroup(_group!); } diff --git a/test/services/group_services_test.dart b/test/services/group_services_test.dart new file mode 100644 index 00000000..2e044431 --- /dev/null +++ b/test/services/group_services_test.dart @@ -0,0 +1,142 @@ +import 'dart:io'; + +import 'package:drift/drift.dart' hide isNotNull, isNull; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; +import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; +import 'package:twonly/src/database/tables/groups.table.dart'; +import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/model/json/userdata.model.dart'; +import 'package:twonly/src/services/api.service.dart'; +import 'package:twonly/src/services/group.services.dart'; +import 'package:twonly/src/services/user.service.dart'; + +class RealHttpOverrides extends HttpOverrides { + @override + HttpClient createHttpClient(SecurityContext? context) { + return super.createHttpClient(context) + ..badCertificateCallback = (cert, host, port) => true; + } +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + HttpOverrides.global = RealHttpOverrides(); + + setUp(() async { + await locator.reset(); + locator + ..registerSingleton( + TwonlyDB.forTesting( + DatabaseConnection( + NativeDatabase.memory(), + closeStreamsSynchronously: true, + ), + ), + ) + ..registerSingleton(UserService()) + ..registerSingleton(ApiService()); + + userService.currentUser = UserData( + userId: 1, + username: 'test_user', + displayName: 'Test User', + subscriptionPlan: 'Free', + )..appVersion = 100; + userService.isUserCreated = true; + AppEnvironment.initTesting(); + // Log.init(); + + expect(apiService.apiHost, 'dev-api.twonly.eu'); + }); + + tearDown(() async { + await twonlyDB.close(); + }); + + group('GroupServices Tests', () { + Future createTestGroup() async { + await twonlyDB.contactsDao.insertContact( + ContactsCompanion.insert( + userId: const Value(2), + username: 'user2', + ), + ); + final members = [ + (await twonlyDB.contactsDao.getContactById(2))!, + ]; + final result = await createNewGroup('Test Group', members); + expect(result, true); + return (await twonlyDB.groupsDao.getAllGroups()).first; + } + + test('Full Group Life Cycle', () async { + late bool success; + // 1. Create Group + var group = await createTestGroup(); + + // 2. Add New Member + await twonlyDB.contactsDao.insertContact( + ContactsCompanion.insert( + userId: const Value(3), + username: 'user3', + ), + ); + success = await addNewGroupMembers(group, [3]); + expect(success, true); + + // 3. Upgrade to Admin + // Generate a real-looking key + group = (await twonlyDB.groupsDao.getGroup(group.groupId))!; + final dummyPubKey = Curve.generateKeyPair().publicKey.serialize(); + success = await manageAdminState(group, dummyPubKey, 2, false); + expect(success, true); + + // 4. Change Time + group = (await twonlyDB.groupsDao.getGroup(group.groupId))!; + success = await updateChatDeletionTime(group, 7200000); + expect(success, true); + + // 5. Remove Member 3 + group = (await twonlyDB.groupsDao.getGroup(group.groupId))!; + success = await removeMemberFromGroup(group, dummyPubKey, 3); + expect(success, true); + + // 6. Remove Member 2 + group = (await twonlyDB.groupsDao.getGroup(group.groupId))!; + success = await removeMemberFromGroup(group, dummyPubKey, 2); + expect(success, true); + + // 7. Leave Group (Myself) + final keyPair = IdentityKeyPair.fromSerialized( + group.myGroupPrivateKey!, + ); + group = (await twonlyDB.groupsDao.getGroup(group.groupId))!; + success = await removeMemberFromGroup( + group, + keyPair.getPublicKey().serialize(), + userService.currentUser.userId, + ); + expect(success, false); + + // Final verification of DB state + final finalGroup = await twonlyDB.groupsDao.getGroup(group.groupId); + expect(finalGroup, isNotNull); + + final history = await twonlyDB.groupsDao + .watchGroupActions(group.groupId) + .first; + + expect(history.length, 7); + expect(history[0].type, GroupActionType.createdGroup); + expect(history[1].type, GroupActionType.addMember); + expect(history[2].type, GroupActionType.promoteToAdmin); + expect(history[3].type, GroupActionType.changeDisplayMaxTime); + expect(history[4].type, GroupActionType.removedMember); + expect(history[5].type, GroupActionType.removedMember); + expect(history[6].type, GroupActionType.removedMember); + }); + }); +}