/* * Copyright (c) 2019 Zender & Kurtz GbR. * * Authors: * Christian Pauly * Marcel Radzio * * This file is part of famedlysdk. * * famedlysdk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * famedlysdk is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with famedlysdk. If not, see . */ import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/src/account_data.dart'; import 'package:famedlysdk/src/client.dart'; import 'package:famedlysdk/src/presence.dart'; import 'package:famedlysdk/src/room.dart'; import 'package:famedlysdk/src/user.dart'; import 'package:famedlysdk/src/sync/event_update.dart'; import 'package:famedlysdk/src/sync/room_update.dart'; import 'package:famedlysdk/src/sync/user_update.dart'; import 'package:famedlysdk/src/utils/matrix_exception.dart'; import 'package:famedlysdk/src/utils/matrix_file.dart'; import 'package:famedlysdk/src/utils/profile.dart'; import 'package:famedlysdk/src/utils/user_device.dart'; import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; import 'fake_matrix_api.dart'; import 'fake_store.dart'; void main() { Client matrix; Future> roomUpdateListFuture; Future> eventUpdateListFuture; Future> userUpdateListFuture; Future> toDeviceUpdateListFuture; const String pickledOlmAccount = "N2v1MkIFGcl0mQpo2OCwSopxPQJ0wnl7oe7PKiT4141AijfdTIhRu+ceXzXKy3Kr00nLqXtRv7kid6hU4a+V0rfJWLL0Y51+3Rp/ORDVnQy+SSeo6Fn4FHcXrxifJEJ0djla5u98fBcJ8BSkhIDmtXRPi5/oJAvpiYn+8zMjFHobOeZUAxYR0VfQ9JzSYBsSovoQ7uFkNks1M4EDUvHtuweStA+EKZvvHZO0SnwRp0Hw7sv8UMYvXw"; const String identityKey = "7rvl3jORJkBiK4XX1e5TnGnqz068XfYJ0W++Ml63rgk"; const String fingerprintKey = "gjL//fyaFHADt9KBADGag8g7F8Up78B/K1zXeiEPLJo"; /// All Tests related to the Login group("FluffyMatrix", () { /// Check if all Elements get created matrix = Client("testclient", debug: true); matrix.httpClient = FakeMatrixApi(); roomUpdateListFuture = matrix.onRoomUpdate.stream.toList(); eventUpdateListFuture = matrix.onEvent.stream.toList(); userUpdateListFuture = matrix.onUserEvent.stream.toList(); toDeviceUpdateListFuture = matrix.onToDeviceEvent.stream.toList(); bool olmEnabled = true; try { olm.init(); olm.Account(); } catch (_) { olmEnabled = false; print("[LibOlm] Failed to load LibOlm: " + _.toString()); } print("[LibOlm] Enabled: $olmEnabled"); test('Login', () async { int presenceCounter = 0; int accountDataCounter = 0; matrix.onPresence.stream.listen((Presence data) { presenceCounter++; }); matrix.onAccountData.stream.listen((AccountData data) { accountDataCounter++; }); expect(matrix.homeserver, null); expect(matrix.matrixVersions, null); try { await matrix.checkServer("https://fakeserver.wrongaddress"); } on FormatException catch (exception) { expect(exception != null, true); } await matrix.checkServer("https://fakeserver.notexisting"); expect(matrix.homeserver, "https://fakeserver.notexisting"); expect(matrix.matrixVersions, ["r0.0.1", "r0.1.0", "r0.2.0", "r0.3.0", "r0.4.0", "r0.5.0"]); final Map resp = await matrix .jsonRequest(type: HTTPType.POST, action: "/client/r0/login", data: { "type": "m.login.password", "user": "test", "password": "1234", "initial_device_display_name": "Fluffy Matrix Client" }); final bool available = await matrix.usernameAvailable("testuser"); expect(available, true); Map registerResponse = await matrix.register(username: "testuser"); expect(registerResponse["user_id"], "@testuser:example.com"); registerResponse = await matrix.register(username: "testuser", kind: "user"); expect(registerResponse["user_id"], "@testuser:example.com"); registerResponse = await matrix.register(username: "testuser", kind: "guest"); expect(registerResponse["user_id"], "@testuser:example.com"); Future loginStateFuture = matrix.onLoginStateChanged.stream.first; Future firstSyncFuture = matrix.onFirstSync.stream.first; Future syncFuture = matrix.onSync.stream.first; matrix.connect( newToken: resp["access_token"], newUserID: resp["user_id"], newHomeserver: matrix.homeserver, newDeviceName: "Text Matrix Client", newDeviceID: resp["device_id"], newMatrixVersions: matrix.matrixVersions, newLazyLoadMembers: matrix.lazyLoadMembers, newOlmAccount: pickledOlmAccount, ); await Future.delayed(Duration(milliseconds: 50)); expect(matrix.accessToken == resp["access_token"], true); expect(matrix.deviceName == "Text Matrix Client", true); expect(matrix.deviceID == resp["device_id"], true); expect(matrix.userID == resp["user_id"], true); LoginState loginState = await loginStateFuture; bool firstSync = await firstSyncFuture; dynamic sync = await syncFuture; expect(loginState, LoginState.logged); expect(firstSync, true); expect(matrix.encryptionEnabled, olmEnabled); if (olmEnabled) { expect(matrix.pickledOlmAccount, pickledOlmAccount); expect(matrix.identityKey, identityKey); expect(matrix.fingerprintKey, fingerprintKey); } expect(sync["next_batch"] == matrix.prevBatch, true); expect(matrix.accountData.length, 3); expect(matrix.getDirectChatFromUserId("@bob:example.com"), "!726s6s6q:example.com"); expect(matrix.rooms[1].directChatMatrixID, "@bob:example.com"); expect(matrix.directChats, matrix.accountData["m.direct"].content); expect(matrix.presences.length, 1); expect(matrix.rooms[1].ephemerals.length, 2); expect(matrix.rooms[1].sessionKeys.length, 1); expect( matrix .rooms[1] .sessionKeys["ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU"] .content["session_key"], "AgAAAAAQcQ6XrFJk6Prm8FikZDqfry/NbDz8Xw7T6e+/9Yf/q3YHIPEQlzv7IZMNcYb51ifkRzFejVvtphS7wwG2FaXIp4XS2obla14iKISR0X74ugB2vyb1AydIHE/zbBQ1ic5s3kgjMFlWpu/S3FQCnCrv+DPFGEt3ERGWxIl3Bl5X53IjPyVkz65oljz2TZESwz0GH/QFvyOOm8ci0q/gceaF3S7Dmafg3dwTKYwcA5xkcc+BLyrLRzB6Hn+oMAqSNSscnm4mTeT5zYibIhrzqyUTMWr32spFtI9dNR/RFSzfCw"); if (olmEnabled) { expect( matrix .rooms[1] .sessionKeys["ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU"] .inboundGroupSession != null, true); } expect(matrix.rooms[1].typingUsers.length, 1); expect(matrix.rooms[1].typingUsers[0].id, "@alice:example.com"); expect(matrix.rooms[1].roomAccountData.length, 3); expect(matrix.rooms[1].encrypted, true); expect(matrix.rooms[1].encryptionAlgorithm, Client.supportedGroupEncryptionAlgorithms.first); expect( matrix.rooms[1].roomAccountData["m.receipt"] .content["@alice:example.com"]["ts"], 1436451550453); expect( matrix.rooms[1].roomAccountData["m.receipt"] .content["@alice:example.com"]["event_id"], "7365636s6r6432:example.com"); expect(matrix.rooms.length, 2); expect(matrix.rooms[1].canonicalAlias, "#famedlyContactDiscovery:${matrix.userID.split(":")[1]}"); final List contacts = await matrix.loadFamedlyContacts(); expect(contacts.length, 1); expect(contacts[0].senderId, "@alice:example.com"); expect( matrix.presences["@alice:example.com"].presence, PresenceType.online); expect(presenceCounter, 1); expect(accountDataCounter, 3); await Future.delayed(Duration(milliseconds: 50)); expect(matrix.userDeviceKeys.length, 2); expect(matrix.userDeviceKeys["@alice:example.com"].outdated, false); expect(matrix.userDeviceKeys["@alice:example.com"].deviceKeys.length, 1); expect( matrix.userDeviceKeys["@alice:example.com"].deviceKeys["JLAFKJWSCS"] .verified, false); matrix.handleSync({ "device_lists": { "changed": [ "@alice:example.com", ], "left": [ "@bob:example.com", ], } }); await Future.delayed(Duration(milliseconds: 50)); expect(matrix.userDeviceKeys.length, 2); expect(matrix.userDeviceKeys["@alice:example.com"].outdated, true); matrix.handleSync({ "rooms": { "join": { "!726s6s6q:example.com": { "state": { "events": [ { "sender": "@alice:example.com", "type": "m.room.canonical_alias", "content": {"alias": ""}, "state_key": "", "origin_server_ts": 1417731086799, "event_id": "66697273743033:example.com" } ] } } } } }); await Future.delayed(Duration(milliseconds: 50)); expect( matrix.getRoomByAlias( "#famedlyContactDiscovery:${matrix.userID.split(":")[1]}"), null); final List altContacts = await matrix.loadFamedlyContacts(); altContacts.forEach((u) => print(u.id)); expect(altContacts.length, 2); expect(altContacts[0].senderId, "@alice:example.com"); }); test('Try to get ErrorResponse', () async { MatrixException expectedException; try { await matrix.jsonRequest( type: HTTPType.PUT, action: "/non/existing/path"); } on MatrixException catch (exception) { expectedException = exception; } expect(expectedException.error, MatrixError.M_UNRECOGNIZED); }); test('Logout', () async { await matrix.jsonRequest( type: HTTPType.POST, action: "/client/r0/logout"); Future loginStateFuture = matrix.onLoginStateChanged.stream.first; matrix.clear(); expect(matrix.accessToken == null, true); expect(matrix.homeserver == null, true); expect(matrix.userID == null, true); expect(matrix.deviceID == null, true); expect(matrix.deviceName == null, true); expect(matrix.matrixVersions == null, true); expect(matrix.lazyLoadMembers == null, true); expect(matrix.prevBatch == null, true); LoginState loginState = await loginStateFuture; expect(loginState, LoginState.loggedOut); }); test('Room Update Test', () async { await matrix.onRoomUpdate.close(); List roomUpdateList = await roomUpdateListFuture; expect(roomUpdateList.length, 3); expect(roomUpdateList[0].id == "!726s6s6q:example.com", true); expect(roomUpdateList[0].membership == Membership.join, true); expect(roomUpdateList[0].prev_batch == "t34-23535_0_0", true); expect(roomUpdateList[0].limitedTimeline == true, true); expect(roomUpdateList[0].notification_count == 2, true); expect(roomUpdateList[0].highlight_count == 2, true); expect(roomUpdateList[1].id == "!696r7674:example.com", true); expect(roomUpdateList[1].membership == Membership.invite, true); expect(roomUpdateList[1].prev_batch == "", true); expect(roomUpdateList[1].limitedTimeline == false, true); expect(roomUpdateList[1].notification_count == 0, true); expect(roomUpdateList[1].highlight_count == 0, true); }); test('Event Update Test', () async { await matrix.onEvent.close(); List eventUpdateList = await eventUpdateListFuture; expect(eventUpdateList.length, 13); expect(eventUpdateList[0].eventType, "m.room.member"); expect(eventUpdateList[0].roomID, "!726s6s6q:example.com"); expect(eventUpdateList[0].type, "state"); expect(eventUpdateList[1].eventType, "m.room.canonical_alias"); expect(eventUpdateList[1].roomID, "!726s6s6q:example.com"); expect(eventUpdateList[1].type, "state"); expect(eventUpdateList[2].eventType, "m.room.encryption"); expect(eventUpdateList[2].roomID, "!726s6s6q:example.com"); expect(eventUpdateList[2].type, "state"); expect(eventUpdateList[3].eventType, "m.room.member"); expect(eventUpdateList[3].roomID, "!726s6s6q:example.com"); expect(eventUpdateList[3].type, "timeline"); expect(eventUpdateList[4].eventType, "m.room.message"); expect(eventUpdateList[4].roomID, "!726s6s6q:example.com"); expect(eventUpdateList[4].type, "timeline"); expect(eventUpdateList[5].eventType, "m.typing"); expect(eventUpdateList[5].roomID, "!726s6s6q:example.com"); expect(eventUpdateList[5].type, "ephemeral"); expect(eventUpdateList[6].eventType, "m.receipt"); expect(eventUpdateList[6].roomID, "!726s6s6q:example.com"); expect(eventUpdateList[6].type, "ephemeral"); expect(eventUpdateList[7].eventType, "m.receipt"); expect(eventUpdateList[7].roomID, "!726s6s6q:example.com"); expect(eventUpdateList[7].type, "account_data"); expect(eventUpdateList[8].eventType, "m.tag"); expect(eventUpdateList[8].roomID, "!726s6s6q:example.com"); expect(eventUpdateList[8].type, "account_data"); expect(eventUpdateList[9].eventType, "org.example.custom.room.config"); expect(eventUpdateList[9].roomID, "!726s6s6q:example.com"); expect(eventUpdateList[9].type, "account_data"); expect(eventUpdateList[10].eventType, "m.room.name"); expect(eventUpdateList[10].roomID, "!696r7674:example.com"); expect(eventUpdateList[10].type, "invite_state"); expect(eventUpdateList[11].eventType, "m.room.member"); expect(eventUpdateList[11].roomID, "!696r7674:example.com"); expect(eventUpdateList[11].type, "invite_state"); }); test('User Update Test', () async { await matrix.onUserEvent.close(); List eventUpdateList = await userUpdateListFuture; expect(eventUpdateList.length, 4); expect(eventUpdateList[0].eventType, "m.presence"); expect(eventUpdateList[0].type, "presence"); expect(eventUpdateList[1].eventType, "m.push_rules"); expect(eventUpdateList[1].type, "account_data"); expect(eventUpdateList[2].eventType, "org.example.custom.config"); expect(eventUpdateList[2].type, "account_data"); }); test('To Device Update Test', () async { await matrix.onToDeviceEvent.close(); List eventUpdateList = await toDeviceUpdateListFuture; expect(eventUpdateList.length, 2); expect(eventUpdateList[0].type, "m.new_device"); expect(eventUpdateList[1].type, "m.room_key"); }); test('Login', () async { matrix = Client("testclient", debug: true); matrix.httpClient = FakeMatrixApi(); roomUpdateListFuture = matrix.onRoomUpdate.stream.toList(); eventUpdateListFuture = matrix.onEvent.stream.toList(); userUpdateListFuture = matrix.onUserEvent.stream.toList(); final bool checkResp = await matrix.checkServer("https://fakeServer.notExisting"); final bool loginResp = await matrix.login("test", "1234"); expect(checkResp, true); expect(loginResp, true); }); test('createRoom', () async { final OpenIdCredentials openId = await matrix.requestOpenIdCredentials(); expect(openId.accessToken, "SomeT0kenHere"); expect(openId.tokenType, "Bearer"); expect(openId.matrixServerName, "example.com"); expect(openId.expiresIn, 3600); }); test('createRoom', () async { final List users = [ User("@alice:fakeServer.notExisting"), User("@bob:fakeServer.notExisting") ]; final String newID = await matrix.createRoom(invite: users); expect(newID, "!1234:fakeServer.notExisting"); }); test('setAvatar', () async { final MatrixFile testFile = MatrixFile(bytes: Uint8List(0), path: "fake/path/file.jpeg"); await matrix.setAvatar(testFile); }); test('setPushers', () async { await matrix.setPushers("abcdefg", "http", "com.famedly.famedlysdk", "famedlySDK", "GitLabCi", "en", "https://examplepushserver.com", format: "event_id_only"); }); test('joinRoomById', () async { final String roomID = "1234"; final Map resp = await matrix.joinRoomById(roomID); expect(resp["room_id"], roomID); }); test('requestUserDevices', () async { final List userDevices = await matrix.requestUserDevices(); expect(userDevices.length, 1); expect(userDevices.first.deviceId, "QBUAZIFURK"); expect(userDevices.first.displayName, "android"); expect(userDevices.first.lastSeenIp, "1.2.3.4"); expect( userDevices.first.lastSeenTs.millisecondsSinceEpoch, 1474491775024); }); test('get archive', () async { List archive = await matrix.archive; await Future.delayed(Duration(milliseconds: 50)); expect(archive.length, 2); expect(archive[0].id, "!5345234234:example.com"); expect(archive[0].membership, Membership.leave); expect(archive[0].name, "The room name"); expect(archive[0].lastMessage, "This is an example text message"); expect(archive[0].roomAccountData.length, 1); expect(archive[1].id, "!5345234235:example.com"); expect(archive[1].membership, Membership.leave); expect(archive[1].name, "The room name 2"); }); test('getProfileFromUserId', () async { final Profile profile = await matrix.getProfileFromUserId("@getme:example.com"); expect(profile.avatarUrl.mxc, "mxc://test"); expect(profile.displayname, "You got me"); expect(profile.content["avatar_url"], profile.avatarUrl.mxc); expect(profile.content["displayname"], profile.displayname); }); test('signJson', () { if (matrix.encryptionEnabled) { expect(matrix.fingerprintKey.isNotEmpty, true); expect(matrix.identityKey.isNotEmpty, true); Map payload = { "unsigned": { "foo": "bar", }, "auth": { "success": true, "mxid": "@john.doe:example.com", "profile": { "display_name": "John Doe", "three_pids": [ {"medium": "email", "address": "john.doe@example.org"}, {"medium": "msisdn", "address": "123456789"} ] } } }; Map payloadWithoutUnsigned = Map.from(payload); payloadWithoutUnsigned.remove("unsigned"); expect( matrix.checkJsonSignature( matrix.fingerprintKey, payload, matrix.userID, matrix.deviceID), false); expect( matrix.checkJsonSignature(matrix.fingerprintKey, payloadWithoutUnsigned, matrix.userID, matrix.deviceID), false); payload = matrix.signJson(payload); payloadWithoutUnsigned = matrix.signJson(payloadWithoutUnsigned); expect(payload["signatures"], payloadWithoutUnsigned["signatures"]); print(payload["signatures"]); expect( matrix.checkJsonSignature( matrix.fingerprintKey, payload, matrix.userID, matrix.deviceID), true); expect( matrix.checkJsonSignature(matrix.fingerprintKey, payloadWithoutUnsigned, matrix.userID, matrix.deviceID), true); } }); test('Track oneTimeKeys', () async { if (matrix.encryptionEnabled) { DateTime last = matrix.lastTimeKeysUploaded ?? DateTime.now(); matrix.handleSync({ "device_one_time_keys_count": {"signed_curve25519": 49} }); await Future.delayed(Duration(milliseconds: 50)); expect( matrix.lastTimeKeysUploaded.millisecondsSinceEpoch > last.millisecondsSinceEpoch, true); } }); test('Test invalidate outboundGroupSessions', () async { if (matrix.encryptionEnabled) { expect(matrix.rooms[1].outboundGroupSession == null, true); await matrix.rooms[1].createOutboundGroupSession(); expect(matrix.rooms[1].outboundGroupSession != null, true); matrix.handleSync({ "device_lists": { "changed": [ "@alice:example.com", ], "left": [ "@bob:example.com", ], } }); await Future.delayed(Duration(milliseconds: 50)); expect(matrix.rooms[1].outboundGroupSession != null, true); } }); test('Test invalidate outboundGroupSessions', () async { if (matrix.encryptionEnabled) { await matrix.rooms[1].clearOutboundGroupSession(wipe: true); expect(matrix.rooms[1].outboundGroupSession == null, true); await matrix.rooms[1].createOutboundGroupSession(); expect(matrix.rooms[1].outboundGroupSession != null, true); matrix.handleSync({ "rooms": { "join": { "!726s6s6q:example.com": { "state": { "events": [ { "content": {"membership": "leave"}, "event_id": "143273582443PhrSn:example.org", "origin_server_ts": 1432735824653, "room_id": "!726s6s6q:example.com", "sender": "@alice:example.com", "state_key": "@alice:example.com", "type": "m.room.member" } ] } } } } }); await Future.delayed(Duration(milliseconds: 50)); expect(matrix.rooms[1].outboundGroupSession != null, true); } }); DeviceKeys deviceKeys = DeviceKeys.fromJson({ "user_id": "@alice:example.com", "device_id": "JLAFKJWSCS", "algorithms": ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"], "keys": { "curve25519:JLAFKJWSCS": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", "ed25519:JLAFKJWSCS": "lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI" }, "signatures": { "@alice:example.com": { "ed25519:JLAFKJWSCS": "dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA" } } }); test('startOutgoingOlmSessions', () async { expect(matrix.olmSessions.length, 0); if (olmEnabled) { await matrix .startOutgoingOlmSessions([deviceKeys], checkSignature: false); expect(matrix.olmSessions.length, 1); expect(matrix.olmSessions.entries.first.key, "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI"); } }); test('sendToDevice', () async { await matrix.sendToDevice( [deviceKeys], "m.message", { "msgtype": "m.text", "body": "Hello world", }); }); test('Logout when token is unknown', () async { Future loginStateFuture = matrix.onLoginStateChanged.stream.first; try { await matrix.jsonRequest( type: HTTPType.DELETE, action: "/unknown/token"); } on MatrixException catch (exception) { expect(exception.error, MatrixError.M_UNKNOWN_TOKEN); } LoginState state = await loginStateFuture; expect(state, LoginState.loggedOut); expect(matrix.isLogged(), false); }); test('Test the fake store api', () async { Client client1 = Client("testclient", debug: true); client1.httpClient = FakeMatrixApi(); FakeStore fakeStore = FakeStore(client1, {}); client1.storeAPI = fakeStore; client1.connect( newToken: "abc123", newUserID: "@test:fakeServer.notExisting", newHomeserver: "https://fakeServer.notExisting", newDeviceName: "Text Matrix Client", newDeviceID: "GHTYAJCE", newMatrixVersions: [ "r0.0.1", "r0.1.0", "r0.2.0", "r0.3.0", "r0.4.0", "r0.5.0" ], newLazyLoadMembers: true, newOlmAccount: pickledOlmAccount, ); await Future.delayed(Duration(milliseconds: 50)); String sessionKey; if (client1.encryptionEnabled) { await client1.rooms[1].createOutboundGroupSession(); sessionKey = client1.rooms[1].outboundGroupSession.session_key(); } expect(client1.isLogged(), true); expect(client1.rooms.length, 2); Client client2 = Client("testclient", debug: true); client2.httpClient = FakeMatrixApi(); client2.storeAPI = FakeStore(client2, fakeStore.storeMap); await Future.delayed(Duration(milliseconds: 100)); expect(client2.isLogged(), true); expect(client2.accessToken, client1.accessToken); expect(client2.userID, client1.userID); expect(client2.homeserver, client1.homeserver); expect(client2.deviceID, client1.deviceID); expect(client2.deviceName, client1.deviceName); expect(client2.matrixVersions, client1.matrixVersions); expect(client2.lazyLoadMembers, client1.lazyLoadMembers); if (client2.encryptionEnabled) { expect(client2.pickledOlmAccount, client1.pickledOlmAccount); expect(json.encode(client2.rooms[1].sessionKeys[sessionKey]), json.encode(client1.rooms[1].sessionKeys[sessionKey])); expect(client2.rooms[1].id, client1.rooms[1].id); expect(client2.rooms[1].outboundGroupSession.session_key(), sessionKey); } }); }); }