fix: race conditions in the SDK and its tests
This commit is contained in:
parent
0b031476b8
commit
6e211f5a81
|
|
@ -417,10 +417,10 @@ class Encryption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispose() {
|
Future<void> dispose() async {
|
||||||
_backgroundTasksRunning = false;
|
_backgroundTasksRunning = false;
|
||||||
keyManager.dispose();
|
keyManager.dispose();
|
||||||
olmManager.dispose();
|
await olmManager.dispose();
|
||||||
keyVerificationManager.dispose();
|
keyVerificationManager.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:matrix/encryption/utils/base64_unpadded.dart';
|
import 'package:matrix/encryption/utils/base64_unpadded.dart';
|
||||||
|
|
@ -85,7 +86,7 @@ class KeyManager {
|
||||||
_inboundGroupSessions.clear();
|
_inboundGroupSessions.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setInboundGroupSession(
|
Future<void> setInboundGroupSession(
|
||||||
String roomId,
|
String roomId,
|
||||||
String sessionId,
|
String sessionId,
|
||||||
String senderKey,
|
String senderKey,
|
||||||
|
|
@ -98,7 +99,7 @@ class KeyManager {
|
||||||
final senderClaimedKeys_ = senderClaimedKeys ?? <String, String>{};
|
final senderClaimedKeys_ = senderClaimedKeys ?? <String, String>{};
|
||||||
final allowedAtIndex_ = allowedAtIndex ?? <String, Map<String, int>>{};
|
final allowedAtIndex_ = allowedAtIndex ?? <String, Map<String, int>>{};
|
||||||
final userId = client.userID;
|
final userId = client.userID;
|
||||||
if (userId == null) return;
|
if (userId == null) return Future.value();
|
||||||
|
|
||||||
if (!senderClaimedKeys_.containsKey('ed25519')) {
|
if (!senderClaimedKeys_.containsKey('ed25519')) {
|
||||||
final device = client.getUserDeviceKeysByCurve25519Key(senderKey);
|
final device = client.getUserDeviceKeysByCurve25519Key(senderKey);
|
||||||
|
|
@ -109,7 +110,7 @@ class KeyManager {
|
||||||
final oldSession =
|
final oldSession =
|
||||||
getInboundGroupSession(roomId, sessionId, senderKey, otherRooms: false);
|
getInboundGroupSession(roomId, sessionId, senderKey, otherRooms: false);
|
||||||
if (content['algorithm'] != AlgorithmTypes.megolmV1AesSha2) {
|
if (content['algorithm'] != AlgorithmTypes.megolmV1AesSha2) {
|
||||||
return;
|
return Future.value();
|
||||||
}
|
}
|
||||||
late olm.InboundGroupSession inboundGroupSession;
|
late olm.InboundGroupSession inboundGroupSession;
|
||||||
try {
|
try {
|
||||||
|
|
@ -122,7 +123,7 @@ class KeyManager {
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
inboundGroupSession.free();
|
inboundGroupSession.free();
|
||||||
Logs().e('[LibOlm] Could not create new InboundGroupSession', e, s);
|
Logs().e('[LibOlm] Could not create new InboundGroupSession', e, s);
|
||||||
return;
|
return Future.value();
|
||||||
}
|
}
|
||||||
final newSession = SessionKey(
|
final newSession = SessionKey(
|
||||||
content: content,
|
content: content,
|
||||||
|
|
@ -148,16 +149,16 @@ class KeyManager {
|
||||||
} else {
|
} else {
|
||||||
// we are gonna keep our old session
|
// we are gonna keep our old session
|
||||||
newSession.dispose();
|
newSession.dispose();
|
||||||
return;
|
return Future.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
final roomInboundGroupSessions =
|
final roomInboundGroupSessions =
|
||||||
_inboundGroupSessions[roomId] ??= <String, SessionKey>{};
|
_inboundGroupSessions[roomId] ??= <String, SessionKey>{};
|
||||||
roomInboundGroupSessions[sessionId] = newSession;
|
roomInboundGroupSessions[sessionId] = newSession;
|
||||||
if (!client.isLogged() || client.encryption == null) {
|
if (!client.isLogged() || client.encryption == null) {
|
||||||
return;
|
return Future.value();
|
||||||
}
|
}
|
||||||
client.database
|
final storeFuture = client.database
|
||||||
?.storeInboundGroupSession(
|
?.storeInboundGroupSession(
|
||||||
roomId,
|
roomId,
|
||||||
sessionId,
|
sessionId,
|
||||||
|
|
@ -188,6 +189,8 @@ class KeyManager {
|
||||||
// and finally broadcast the new session
|
// and finally broadcast the new session
|
||||||
room.onSessionKeyReceived.add(sessionId);
|
room.onSessionKeyReceived.add(sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return storeFuture ?? Future.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
SessionKey? getInboundGroupSession(
|
SessionKey? getInboundGroupSession(
|
||||||
|
|
@ -503,7 +506,7 @@ class KeyManager {
|
||||||
allowedAtIndex[device.userId]![device.curve25519Key!] =
|
allowedAtIndex[device.userId]![device.curve25519Key!] =
|
||||||
outboundGroupSession.message_index();
|
outboundGroupSession.message_index();
|
||||||
}
|
}
|
||||||
setInboundGroupSession(
|
await setInboundGroupSession(
|
||||||
roomId, rawSession['session_id'], encryption.identityKey!, rawSession,
|
roomId, rawSession['session_id'], encryption.identityKey!, rawSession,
|
||||||
allowedAtIndex: allowedAtIndex);
|
allowedAtIndex: allowedAtIndex);
|
||||||
final sess = OutboundGroupSession(
|
final sess = OutboundGroupSession(
|
||||||
|
|
@ -612,7 +615,7 @@ class KeyManager {
|
||||||
if (decrypted != null) {
|
if (decrypted != null) {
|
||||||
decrypted['session_id'] = sessionId;
|
decrypted['session_id'] = sessionId;
|
||||||
decrypted['room_id'] = roomId;
|
decrypted['room_id'] = roomId;
|
||||||
setInboundGroupSession(
|
await setInboundGroupSession(
|
||||||
roomId, sessionId, decrypted['sender_key'], decrypted,
|
roomId, sessionId, decrypted['sender_key'], decrypted,
|
||||||
forwarded: true,
|
forwarded: true,
|
||||||
senderClaimedKeys: decrypted['sender_claimed_keys'] != null
|
senderClaimedKeys: decrypted['sender_claimed_keys'] != null
|
||||||
|
|
@ -901,7 +904,7 @@ class KeyManager {
|
||||||
.add(encryptedContent['sender_key']);
|
.add(encryptedContent['sender_key']);
|
||||||
// TODO: verify that the keys work to decrypt a message
|
// TODO: verify that the keys work to decrypt a message
|
||||||
// alright, all checks out, let's go ahead and store this session
|
// alright, all checks out, let's go ahead and store this session
|
||||||
setInboundGroupSession(
|
await setInboundGroupSession(
|
||||||
request.room.id, request.sessionId, request.senderKey, event.content,
|
request.room.id, request.sessionId, request.senderKey, event.content,
|
||||||
forwarded: true,
|
forwarded: true,
|
||||||
senderClaimedKeys: {
|
senderClaimedKeys: {
|
||||||
|
|
@ -946,7 +949,7 @@ class KeyManager {
|
||||||
event.content['sender_claimed_ed25519_key'] = sender_ed25519;
|
event.content['sender_claimed_ed25519_key'] = sender_ed25519;
|
||||||
}
|
}
|
||||||
Logs().v('[KeyManager] Keeping room key');
|
Logs().v('[KeyManager] Keeping room key');
|
||||||
setInboundGroupSession(
|
await setInboundGroupSession(
|
||||||
roomId, sessionId, encryptedContent['sender_key'], event.content,
|
roomId, sessionId, encryptedContent['sender_key'], event.content,
|
||||||
forwarded: false);
|
forwarded: false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:async/async.dart';
|
||||||
import 'package:canonical_json/canonical_json.dart';
|
import 'package:canonical_json/canonical_json.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
@ -109,6 +110,7 @@ class OlmManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _uploadKeysLock = false;
|
bool _uploadKeysLock = false;
|
||||||
|
CancelableOperation<Map<String, int>>? currentUpload;
|
||||||
|
|
||||||
/// Generates new one time keys, signs everything and upload it to the server.
|
/// Generates new one time keys, signs everything and upload it to the server.
|
||||||
Future<bool> uploadKeys({
|
Future<bool> uploadKeys({
|
||||||
|
|
@ -211,13 +213,20 @@ class OlmManager {
|
||||||
}
|
}
|
||||||
// Workaround: Make sure we stop if we got logged out in the meantime.
|
// Workaround: Make sure we stop if we got logged out in the meantime.
|
||||||
if (!client.isLogged()) return true;
|
if (!client.isLogged()) return true;
|
||||||
final response = await client.uploadKeys(
|
final currentUpload =
|
||||||
|
this.currentUpload = CancelableOperation.fromFuture(client.uploadKeys(
|
||||||
deviceKeys: uploadDeviceKeys
|
deviceKeys: uploadDeviceKeys
|
||||||
? MatrixDeviceKeys.fromJson(keysContent['device_keys'])
|
? MatrixDeviceKeys.fromJson(keysContent['device_keys'])
|
||||||
: null,
|
: null,
|
||||||
oneTimeKeys: signedOneTimeKeys,
|
oneTimeKeys: signedOneTimeKeys,
|
||||||
fallbackKeys: signedFallbackKeys,
|
fallbackKeys: signedFallbackKeys,
|
||||||
);
|
));
|
||||||
|
final response = await currentUpload.valueOrCancellation();
|
||||||
|
if (response == null) {
|
||||||
|
_uploadKeysLock = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// mark the OTKs as published and save that to datbase
|
// mark the OTKs as published and save that to datbase
|
||||||
_olmAccount.mark_keys_as_published();
|
_olmAccount.mark_keys_as_published();
|
||||||
if (updateDatabase) {
|
if (updateDatabase) {
|
||||||
|
|
@ -231,8 +240,8 @@ class OlmManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleDeviceOneTimeKeysCount(
|
Future<void> handleDeviceOneTimeKeysCount(
|
||||||
Map<String, int>? countJson, List<String>? unusedFallbackKeyTypes) {
|
Map<String, int>? countJson, List<String>? unusedFallbackKeyTypes) async {
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -255,13 +264,13 @@ class OlmManager {
|
||||||
final requestingKeysFrom = {
|
final requestingKeysFrom = {
|
||||||
client.userID!: {client.deviceID!: 'signed_curve25519'}
|
client.userID!: {client.deviceID!: 'signed_curve25519'}
|
||||||
};
|
};
|
||||||
client.claimKeys(requestingKeysFrom, timeout: 10000);
|
await client.claimKeys(requestingKeysFrom, timeout: 10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only upload keys if they are less than half of the max or we have no unused fallback key
|
// Only upload keys if they are less than half of the max or we have no unused fallback key
|
||||||
if (keyCount < (_olmAccount!.max_number_of_one_time_keys() / 2) ||
|
if (keyCount < (_olmAccount!.max_number_of_one_time_keys() / 2) ||
|
||||||
!unusedFallbackKey) {
|
!unusedFallbackKey) {
|
||||||
uploadKeys(
|
await uploadKeys(
|
||||||
oldKeyCount: keyCount < (_olmAccount!.max_number_of_one_time_keys() / 2)
|
oldKeyCount: keyCount < (_olmAccount!.max_number_of_one_time_keys() / 2)
|
||||||
? keyCount
|
? keyCount
|
||||||
: null,
|
: null,
|
||||||
|
|
@ -293,7 +302,7 @@ class OlmManager {
|
||||||
DateTime.now().millisecondsSinceEpoch);
|
DateTime.now().millisecondsSinceEpoch);
|
||||||
}
|
}
|
||||||
|
|
||||||
ToDeviceEvent _decryptToDeviceEvent(ToDeviceEvent event) {
|
Future<ToDeviceEvent> _decryptToDeviceEvent(ToDeviceEvent event) async {
|
||||||
if (event.type != EventTypes.Encrypted) {
|
if (event.type != EventTypes.Encrypted) {
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
@ -341,12 +350,12 @@ class OlmManager {
|
||||||
throw DecryptException(
|
throw DecryptException(
|
||||||
DecryptException.decryptionFailed, e.toString());
|
DecryptException.decryptionFailed, e.toString());
|
||||||
}
|
}
|
||||||
updateSessionUsage(session);
|
await updateSessionUsage(session);
|
||||||
break;
|
break;
|
||||||
} else if (type == 1) {
|
} else if (type == 1) {
|
||||||
try {
|
try {
|
||||||
plaintext = session.session!.decrypt(type, body);
|
plaintext = session.session!.decrypt(type, body);
|
||||||
updateSessionUsage(session);
|
await updateSessionUsage(session);
|
||||||
break;
|
break;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
plaintext = null;
|
plaintext = null;
|
||||||
|
|
@ -363,16 +372,16 @@ class OlmManager {
|
||||||
try {
|
try {
|
||||||
newSession.create_inbound_from(_olmAccount!, senderKey, body);
|
newSession.create_inbound_from(_olmAccount!, senderKey, body);
|
||||||
_olmAccount!.remove_one_time_keys(newSession);
|
_olmAccount!.remove_one_time_keys(newSession);
|
||||||
client.database?.updateClientKeys(pickledOlmAccount!);
|
await client.database?.updateClientKeys(pickledOlmAccount!);
|
||||||
plaintext = newSession.decrypt(type, body);
|
plaintext = newSession.decrypt(type, body);
|
||||||
runInRoot(() => storeOlmSession(OlmSession(
|
await runInRoot(() => storeOlmSession(OlmSession(
|
||||||
key: client.userID!,
|
key: client.userID!,
|
||||||
identityKey: senderKey,
|
identityKey: senderKey,
|
||||||
sessionId: newSession.session_id(),
|
sessionId: newSession.session_id(),
|
||||||
session: newSession,
|
session: newSession,
|
||||||
lastReceived: DateTime.now(),
|
lastReceived: DateTime.now(),
|
||||||
)));
|
)));
|
||||||
updateSessionUsage();
|
await updateSessionUsage();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
newSession.free();
|
newSession.free();
|
||||||
throw DecryptException(DecryptException.decryptionFailed, e.toString());
|
throw DecryptException(DecryptException.decryptionFailed, e.toString());
|
||||||
|
|
@ -479,7 +488,7 @@ class OlmManager {
|
||||||
await loadFromDb();
|
await loadFromDb();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
event = _decryptToDeviceEvent(event);
|
event = await _decryptToDeviceEvent(event);
|
||||||
if (event.type != EventTypes.Encrypted || !(await loadFromDb())) {
|
if (event.type != EventTypes.Encrypted || !(await loadFromDb())) {
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
@ -566,14 +575,14 @@ class OlmManager {
|
||||||
final encryptResult = sess.first.session!.encrypt(json.encode(fullPayload));
|
final encryptResult = sess.first.session!.encrypt(json.encode(fullPayload));
|
||||||
await storeOlmSession(sess.first);
|
await storeOlmSession(sess.first);
|
||||||
if (client.database != null) {
|
if (client.database != null) {
|
||||||
// ignore: unawaited_futures
|
await runInRoot(
|
||||||
runInRoot(() => client.database?.setLastSentMessageUserDeviceKey(
|
() async => client.database?.setLastSentMessageUserDeviceKey(
|
||||||
json.encode({
|
json.encode({
|
||||||
'type': type,
|
'type': type,
|
||||||
'content': payload,
|
'content': payload,
|
||||||
}),
|
}),
|
||||||
device.userId,
|
device.userId,
|
||||||
device.deviceId!));
|
device.deviceId!));
|
||||||
}
|
}
|
||||||
final encryptedBody = <String, dynamic>{
|
final encryptedBody = <String, dynamic>{
|
||||||
'algorithm': AlgorithmTypes.olmV1Curve25519AesSha2,
|
'algorithm': AlgorithmTypes.olmV1Curve25519AesSha2,
|
||||||
|
|
@ -651,7 +660,8 @@ class OlmManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispose() {
|
Future<void> dispose() async {
|
||||||
|
await currentUpload?.cancel();
|
||||||
for (final sessions in olmSessions.values) {
|
for (final sessions in olmSessions.values) {
|
||||||
for (final sess in sessions) {
|
for (final sess in sessions) {
|
||||||
sess.dispose();
|
sess.dispose();
|
||||||
|
|
|
||||||
|
|
@ -221,6 +221,16 @@ class KeyVerification {
|
||||||
await cancel('m.unknown_method');
|
await cancel('m.unknown_method');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensure we have the other sides keys
|
||||||
|
if (client.userDeviceKeys[userId]?.deviceKeys[deviceId!] == null) {
|
||||||
|
await client.updateUserDeviceKeys(additionalUsers: {userId});
|
||||||
|
if (client.userDeviceKeys[userId]?.deviceKeys[deviceId!] == null) {
|
||||||
|
await cancel('im.fluffychat.unknown_device');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setState(KeyVerificationState.askAccept);
|
setState(KeyVerificationState.askAccept);
|
||||||
break;
|
break;
|
||||||
case 'm.key.verification.ready':
|
case 'm.key.verification.ready':
|
||||||
|
|
@ -248,6 +258,16 @@ class KeyVerification {
|
||||||
await cancel('m.unknown_method');
|
await cancel('m.unknown_method');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensure we have the other sides keys
|
||||||
|
if (client.userDeviceKeys[userId]?.deviceKeys[deviceId!] == null) {
|
||||||
|
await client.updateUserDeviceKeys(additionalUsers: {userId});
|
||||||
|
if (client.userDeviceKeys[userId]?.deviceKeys[deviceId!] == null) {
|
||||||
|
await cancel('im.fluffychat.unknown_device');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// as both parties can send a start, the last step being "ready" is race-condition prone
|
// as both parties can send a start, the last step being "ready" is race-condition prone
|
||||||
// as such, we better set it *before* we send our start
|
// as such, we better set it *before* we send our start
|
||||||
lastStep = type;
|
lastStep = type;
|
||||||
|
|
@ -291,6 +311,16 @@ class KeyVerification {
|
||||||
await cancel('m.unknown_method');
|
await cancel('m.unknown_method');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensure we have the other sides keys
|
||||||
|
if (client.userDeviceKeys[userId]?.deviceKeys[deviceId!] == null) {
|
||||||
|
await client.updateUserDeviceKeys(additionalUsers: {userId});
|
||||||
|
if (client.userDeviceKeys[userId]?.deviceKeys[deviceId!] == null) {
|
||||||
|
await cancel('im.fluffychat.unknown_device');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
method = _makeVerificationMethod(payload['method'], this);
|
method = _makeVerificationMethod(payload['method'], this);
|
||||||
if (lastStep == null) {
|
if (lastStep == null) {
|
||||||
// validate the start time
|
// validate the start time
|
||||||
|
|
@ -352,17 +382,17 @@ class KeyVerification {
|
||||||
String? recoveryKey,
|
String? recoveryKey,
|
||||||
String? keyOrPassphrase,
|
String? keyOrPassphrase,
|
||||||
bool skip = false}) async {
|
bool skip = false}) async {
|
||||||
final next = () {
|
final next = () async {
|
||||||
if (_nextAction == 'request') {
|
if (_nextAction == 'request') {
|
||||||
sendStart();
|
await sendStart();
|
||||||
} else if (_nextAction == 'done') {
|
} else if (_nextAction == 'done') {
|
||||||
// and now let's sign them all in the background
|
// and now let's sign them all in the background
|
||||||
encryption.crossSigning.sign(_verifiedDevices);
|
unawaited(encryption.crossSigning.sign(_verifiedDevices));
|
||||||
setState(KeyVerificationState.done);
|
setState(KeyVerificationState.done);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (skip) {
|
if (skip) {
|
||||||
next();
|
await next();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final handle = encryption.ssss.open(EventTypes.CrossSigningUserSigning);
|
final handle = encryption.ssss.open(EventTypes.CrossSigningUserSigning);
|
||||||
|
|
@ -371,7 +401,7 @@ class KeyVerification {
|
||||||
recoveryKey: recoveryKey,
|
recoveryKey: recoveryKey,
|
||||||
keyOrPassphrase: keyOrPassphrase);
|
keyOrPassphrase: keyOrPassphrase);
|
||||||
await handle.maybeCacheAll();
|
await handle.maybeCacheAll();
|
||||||
next();
|
await next();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// called when the user accepts an incoming verification
|
/// called when the user accepts an incoming verification
|
||||||
|
|
@ -512,9 +542,9 @@ class KeyVerification {
|
||||||
encryption.crossSigning.signable(_verifiedDevices)) {
|
encryption.crossSigning.signable(_verifiedDevices)) {
|
||||||
// these keys can be signed! Let's do so
|
// these keys can be signed! Let's do so
|
||||||
if (await encryption.crossSigning.isCached()) {
|
if (await encryption.crossSigning.isCached()) {
|
||||||
// and now let's sign them all in the background
|
// we want to make sure the verification state is correct for the other party after this event is handled.
|
||||||
// ignore: unawaited_futures
|
// Otherwise the verification dialog might be stuck in an unverified but done state for a bit.
|
||||||
encryption.crossSigning.sign(_verifiedDevices);
|
await encryption.crossSigning.sign(_verifiedDevices);
|
||||||
} else if (!wasUnknownSession) {
|
} else if (!wasUnknownSession) {
|
||||||
askingSSSS = true;
|
askingSSSS = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1307,7 +1307,7 @@ class Client extends MatrixApi {
|
||||||
if (isLogged()) return;
|
if (isLogged()) return;
|
||||||
}
|
}
|
||||||
// we aren't logged in
|
// we aren't logged in
|
||||||
encryption?.dispose();
|
await encryption?.dispose();
|
||||||
encryption = null;
|
encryption = null;
|
||||||
onLoginStateChanged.add(LoginState.loggedOut);
|
onLoginStateChanged.add(LoginState.loggedOut);
|
||||||
Logs().i('User is not logged in.');
|
Logs().i('User is not logged in.');
|
||||||
|
|
@ -1315,14 +1315,14 @@ class Client extends MatrixApi {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
encryption?.dispose();
|
await encryption?.dispose();
|
||||||
try {
|
try {
|
||||||
// make sure to throw an exception if libolm doesn't exist
|
// make sure to throw an exception if libolm doesn't exist
|
||||||
await olm.init();
|
await olm.init();
|
||||||
olm.get_library_version();
|
olm.get_library_version();
|
||||||
encryption = Encryption(client: this);
|
encryption = Encryption(client: this);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
encryption?.dispose();
|
await encryption?.dispose();
|
||||||
encryption = null;
|
encryption = null;
|
||||||
}
|
}
|
||||||
await encryption?.init(olmAccount);
|
await encryption?.init(olmAccount);
|
||||||
|
|
@ -1408,7 +1408,7 @@ class Client extends MatrixApi {
|
||||||
_id = accessToken = syncFilterId =
|
_id = accessToken = syncFilterId =
|
||||||
homeserver = _userID = _deviceID = _deviceName = prevBatch = null;
|
homeserver = _userID = _deviceID = _deviceName = prevBatch = null;
|
||||||
_rooms = [];
|
_rooms = [];
|
||||||
encryption?.dispose();
|
await encryption?.dispose();
|
||||||
encryption = null;
|
encryption = null;
|
||||||
final databaseDestroyer = this.databaseDestroyer;
|
final databaseDestroyer = this.databaseDestroyer;
|
||||||
if (databaseDestroyer != null) {
|
if (databaseDestroyer != null) {
|
||||||
|
|
@ -1443,18 +1443,14 @@ class Client extends MatrixApi {
|
||||||
return _sync();
|
return _sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _sync() async {
|
Future<void> _sync() {
|
||||||
if (_currentSync == null) {
|
final _currentSync = this._currentSync ??= _innerSync().whenComplete(() {
|
||||||
final _currentSync = this._currentSync = _innerSync();
|
this._currentSync = null;
|
||||||
// ignore: unawaited_futures
|
if (_backgroundSync && isLogged() && !_disposed) {
|
||||||
_currentSync.whenComplete(() {
|
_sync();
|
||||||
this._currentSync = null;
|
}
|
||||||
if (_backgroundSync && isLogged() && !_disposed) {
|
});
|
||||||
_sync();
|
return _currentSync;
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await _currentSync;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Presence that is set on sync.
|
/// Presence that is set on sync.
|
||||||
|
|
@ -2112,7 +2108,7 @@ class Client extends MatrixApi {
|
||||||
|
|
||||||
final Map<String, DateTime> _keyQueryFailures = {};
|
final Map<String, DateTime> _keyQueryFailures = {};
|
||||||
|
|
||||||
Future<void> updateUserDeviceKeys() async {
|
Future<void> updateUserDeviceKeys({Set<String>? additionalUsers}) async {
|
||||||
try {
|
try {
|
||||||
final database = this.database;
|
final database = this.database;
|
||||||
if (!isLogged() || database == null) return;
|
if (!isLogged() || database == null) return;
|
||||||
|
|
@ -2120,6 +2116,7 @@ class Client extends MatrixApi {
|
||||||
final trackedUserIds = await _getUserIdsInEncryptedRooms();
|
final trackedUserIds = await _getUserIdsInEncryptedRooms();
|
||||||
if (!isLogged()) return;
|
if (!isLogged()) return;
|
||||||
trackedUserIds.add(userID!);
|
trackedUserIds.add(userID!);
|
||||||
|
if (additionalUsers != null) trackedUserIds.addAll(additionalUsers);
|
||||||
|
|
||||||
// Remove all userIds we no longer need to track the devices of.
|
// Remove all userIds we no longer need to track the devices of.
|
||||||
_userDeviceKeys
|
_userDeviceKeys
|
||||||
|
|
@ -2511,8 +2508,7 @@ class Client extends MatrixApi {
|
||||||
? deviceKeys.length
|
? deviceKeys.length
|
||||||
: i + chunkSize);
|
: i + chunkSize);
|
||||||
// and send
|
// and send
|
||||||
// ignore: unawaited_futures
|
await sendToDeviceEncrypted(chunk, eventType, message);
|
||||||
sendToDeviceEncrypted(chunk, eventType, message);
|
|
||||||
}
|
}
|
||||||
}();
|
}();
|
||||||
}
|
}
|
||||||
|
|
@ -2665,14 +2661,15 @@ class Client extends MatrixApi {
|
||||||
Future<void> dispose({bool closeDatabase = true}) async {
|
Future<void> dispose({bool closeDatabase = true}) async {
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
await abortSync();
|
await abortSync();
|
||||||
encryption?.dispose();
|
await encryption?.dispose();
|
||||||
encryption = null;
|
encryption = null;
|
||||||
try {
|
try {
|
||||||
if (closeDatabase) {
|
if (closeDatabase) {
|
||||||
|
final database = _database;
|
||||||
|
_database = null;
|
||||||
await database
|
await database
|
||||||
?.close()
|
?.close()
|
||||||
.catchError((e, s) => Logs().w('Failed to close database: ', e, s));
|
.catchError((e, s) => Logs().w('Failed to close database: ', e, s));
|
||||||
_database = null;
|
|
||||||
}
|
}
|
||||||
} catch (error, stacktrace) {
|
} catch (error, stacktrace) {
|
||||||
Logs().w('Failed to close database: ', error, stacktrace);
|
Logs().w('Failed to close database: ', error, stacktrace);
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ environment:
|
||||||
sdk: ">=2.12.0 <3.0.0"
|
sdk: ">=2.12.0 <3.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
async: ^2.8.0
|
||||||
blurhash_dart: ^1.1.0
|
blurhash_dart: ^1.1.0
|
||||||
http: ^0.13.0
|
http: ^0.13.0
|
||||||
mime: ^1.0.0
|
mime: ^1.0.0
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -288,9 +288,8 @@ void main() {
|
||||||
.getInboundGroupSession(roomId, sessionId, senderKey) !=
|
.getInboundGroupSession(roomId, sessionId, senderKey) !=
|
||||||
null,
|
null,
|
||||||
false);
|
false);
|
||||||
client.encryption!.keyManager
|
await client.encryption!.keyManager
|
||||||
.setInboundGroupSession(roomId, sessionId, senderKey, sessionContent);
|
.setInboundGroupSession(roomId, sessionId, senderKey, sessionContent);
|
||||||
await Future.delayed(Duration(milliseconds: 10));
|
|
||||||
expect(
|
expect(
|
||||||
client.encryption!.keyManager
|
client.encryption!.keyManager
|
||||||
.getInboundGroupSession(roomId, sessionId, senderKey) !=
|
.getInboundGroupSession(roomId, sessionId, senderKey) !=
|
||||||
|
|
@ -395,7 +394,7 @@ void main() {
|
||||||
'sender_key': senderKey,
|
'sender_key': senderKey,
|
||||||
'sender_claimed_ed25519_key': client.fingerprintKey,
|
'sender_claimed_ed25519_key': client.fingerprintKey,
|
||||||
};
|
};
|
||||||
client.encryption!.keyManager.setInboundGroupSession(
|
await client.encryption!.keyManager.setInboundGroupSession(
|
||||||
roomId, sessionId, senderKey, sessionPayload,
|
roomId, sessionId, senderKey, sessionPayload,
|
||||||
forwarded: true);
|
forwarded: true);
|
||||||
expect(
|
expect(
|
||||||
|
|
@ -421,7 +420,7 @@ void main() {
|
||||||
'sender_key': senderKey,
|
'sender_key': senderKey,
|
||||||
'sender_claimed_ed25519_key': client.fingerprintKey,
|
'sender_claimed_ed25519_key': client.fingerprintKey,
|
||||||
};
|
};
|
||||||
client.encryption!.keyManager.setInboundGroupSession(
|
await client.encryption!.keyManager.setInboundGroupSession(
|
||||||
roomId, sessionId, senderKey, sessionPayload,
|
roomId, sessionId, senderKey, sessionPayload,
|
||||||
forwarded: true);
|
forwarded: true);
|
||||||
expect(
|
expect(
|
||||||
|
|
@ -447,7 +446,7 @@ void main() {
|
||||||
'sender_key': senderKey,
|
'sender_key': senderKey,
|
||||||
'sender_claimed_ed25519_key': client.fingerprintKey,
|
'sender_claimed_ed25519_key': client.fingerprintKey,
|
||||||
};
|
};
|
||||||
client.encryption!.keyManager.setInboundGroupSession(
|
await client.encryption!.keyManager.setInboundGroupSession(
|
||||||
roomId, sessionId, senderKey, sessionPayload,
|
roomId, sessionId, senderKey, sessionPayload,
|
||||||
forwarded: true);
|
forwarded: true);
|
||||||
expect(
|
expect(
|
||||||
|
|
@ -473,7 +472,7 @@ void main() {
|
||||||
'sender_key': senderKey,
|
'sender_key': senderKey,
|
||||||
'sender_claimed_ed25519_key': client.fingerprintKey,
|
'sender_claimed_ed25519_key': client.fingerprintKey,
|
||||||
};
|
};
|
||||||
client.encryption!.keyManager.setInboundGroupSession(
|
await client.encryption!.keyManager.setInboundGroupSession(
|
||||||
roomId, sessionId, senderKey, sessionPayload,
|
roomId, sessionId, senderKey, sessionPayload,
|
||||||
forwarded: true);
|
forwarded: true);
|
||||||
expect(
|
expect(
|
||||||
|
|
@ -499,7 +498,7 @@ void main() {
|
||||||
'sender_key': senderKey,
|
'sender_key': senderKey,
|
||||||
'sender_claimed_ed25519_key': client.fingerprintKey,
|
'sender_claimed_ed25519_key': client.fingerprintKey,
|
||||||
};
|
};
|
||||||
client.encryption!.keyManager.setInboundGroupSession(
|
await client.encryption!.keyManager.setInboundGroupSession(
|
||||||
roomId, sessionId, senderKey, sessionPayload,
|
roomId, sessionId, senderKey, sessionPayload,
|
||||||
forwarded: true);
|
forwarded: true);
|
||||||
expect(
|
expect(
|
||||||
|
|
@ -539,8 +538,9 @@ void main() {
|
||||||
.remove('JLAFKJWSCS');
|
.remove('JLAFKJWSCS');
|
||||||
|
|
||||||
// Alice adds her device with same device ID but different keys
|
// Alice adds her device with same device ID but different keys
|
||||||
final oldResp = FakeMatrixApi.api['POST']?['/client/v3/keys/query'](null);
|
final oldResp =
|
||||||
FakeMatrixApi.api['POST']?['/client/v3/keys/query'] = (_) {
|
FakeMatrixApi.currentApi?.api['POST']?['/client/v3/keys/query'](null);
|
||||||
|
FakeMatrixApi.currentApi?.api['POST']?['/client/v3/keys/query'] = (_) {
|
||||||
oldResp['device_keys']['@alice:example.com']['JLAFKJWSCS'] = {
|
oldResp['device_keys']['@alice:example.com']['JLAFKJWSCS'] = {
|
||||||
'user_id': '@alice:example.com',
|
'user_id': '@alice:example.com',
|
||||||
'device_id': 'JLAFKJWSCS',
|
'device_id': 'JLAFKJWSCS',
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
@ -59,11 +60,22 @@ EventUpdate getLastSentEvent(KeyVerification req) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() async {
|
||||||
|
var olmEnabled = true;
|
||||||
|
try {
|
||||||
|
await olm.init();
|
||||||
|
olm.get_library_version();
|
||||||
|
} catch (e) {
|
||||||
|
olmEnabled = false;
|
||||||
|
Logs().w('[LibOlm] Failed to load LibOlm', e);
|
||||||
|
}
|
||||||
|
Logs().i('[LibOlm] Enabled: $olmEnabled');
|
||||||
|
|
||||||
|
final dynamic skip = olmEnabled ? false : 'olm library not available';
|
||||||
|
|
||||||
/// All Tests related to the ChatTime
|
/// All Tests related to the ChatTime
|
||||||
group('Key Verification', () {
|
group('Key Verification', () {
|
||||||
Logs().level = Level.error;
|
Logs().level = Level.error;
|
||||||
var olmEnabled = true;
|
|
||||||
|
|
||||||
// key @othertest:fakeServer.notExisting
|
// key @othertest:fakeServer.notExisting
|
||||||
const otherPickledOlmAccount =
|
const otherPickledOlmAccount =
|
||||||
|
|
@ -72,21 +84,11 @@ void main() {
|
||||||
late Client client1;
|
late Client client1;
|
||||||
late Client client2;
|
late Client client2;
|
||||||
|
|
||||||
test('setupClient', () async {
|
setUp(() async {
|
||||||
try {
|
|
||||||
await olm.init();
|
|
||||||
olm.get_library_version();
|
|
||||||
} catch (e) {
|
|
||||||
olmEnabled = false;
|
|
||||||
Logs().w('[LibOlm] Failed to load LibOlm', e);
|
|
||||||
}
|
|
||||||
Logs().i('[LibOlm] Enabled: $olmEnabled');
|
|
||||||
if (!olmEnabled) return;
|
|
||||||
|
|
||||||
client1 = await getClient();
|
client1 = await getClient();
|
||||||
client2 = Client(
|
client2 = Client(
|
||||||
'othertestclient',
|
'othertestclient',
|
||||||
httpClient: FakeMatrixApi(),
|
httpClient: FakeMatrixApi.currentApi!,
|
||||||
databaseBuilder: getDatabase,
|
databaseBuilder: getDatabase,
|
||||||
);
|
);
|
||||||
await client2.checkHomeserver(Uri.parse('https://fakeserver.notexisting'),
|
await client2.checkHomeserver(Uri.parse('https://fakeserver.notexisting'),
|
||||||
|
|
@ -109,9 +111,12 @@ void main() {
|
||||||
KeyVerificationMethod.numbers
|
KeyVerificationMethod.numbers
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
tearDown(() async {
|
||||||
|
await client1.dispose(closeDatabase: true);
|
||||||
|
await client2.dispose(closeDatabase: true);
|
||||||
|
});
|
||||||
|
|
||||||
test('Run emoji / number verification', () async {
|
test('Run emoji / number verification', () async {
|
||||||
if (!olmEnabled) return;
|
|
||||||
// for a full run we test in-room verification in a cleartext room
|
// for a full run we test in-room verification in a cleartext room
|
||||||
// because then we can easily intercept the payloads and inject in the other client
|
// because then we can easily intercept the payloads and inject in the other client
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
|
|
@ -122,15 +127,17 @@ void main() {
|
||||||
await client1.userDeviceKeys[client2.userID]!.startVerification(
|
await client1.userDeviceKeys[client2.userID]!.startVerification(
|
||||||
newDirectChatEnableEncryption: false,
|
newDirectChatEnableEncryption: false,
|
||||||
);
|
);
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message'));
|
||||||
var evt = getLastSentEvent(req1);
|
var evt = getLastSentEvent(req1);
|
||||||
expect(req1.state, KeyVerificationState.waitingAccept);
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
||||||
|
|
||||||
late KeyVerification req2;
|
final comp = Completer<KeyVerification>();
|
||||||
final sub = client2.onKeyVerificationRequest.stream.listen((req) {
|
final sub = client2.onKeyVerificationRequest.stream.listen((req) {
|
||||||
req2 = req;
|
comp.complete(req);
|
||||||
});
|
});
|
||||||
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
await Future.delayed(Duration(milliseconds: 10));
|
final req2 = await comp.future;
|
||||||
await sub.cancel();
|
await sub.cancel();
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
|
|
@ -141,27 +148,37 @@ void main() {
|
||||||
// send ready
|
// send ready
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await req2.acceptVerification();
|
await req2.acceptVerification();
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.ready'));
|
||||||
evt = getLastSentEvent(req2);
|
evt = getLastSentEvent(req2);
|
||||||
expect(req2.state, KeyVerificationState.waitingAccept);
|
expect(req2.state, KeyVerificationState.waitingAccept);
|
||||||
|
|
||||||
// send start
|
// send start
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.start'));
|
||||||
evt = getLastSentEvent(req1);
|
evt = getLastSentEvent(req1);
|
||||||
|
|
||||||
// send accept
|
// send accept
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.accept'));
|
||||||
evt = getLastSentEvent(req2);
|
evt = getLastSentEvent(req2);
|
||||||
|
|
||||||
// send key
|
// send key
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.key'));
|
||||||
evt = getLastSentEvent(req1);
|
evt = getLastSentEvent(req1);
|
||||||
|
|
||||||
// send key
|
// send key
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.key'));
|
||||||
evt = getLastSentEvent(req2);
|
evt = getLastSentEvent(req2);
|
||||||
|
|
||||||
// receive last key
|
// receive last key
|
||||||
|
|
@ -196,13 +213,22 @@ void main() {
|
||||||
await req1.acceptSas();
|
await req1.acceptSas();
|
||||||
evt = getLastSentEvent(req1);
|
evt = getLastSentEvent(req1);
|
||||||
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.mac'));
|
||||||
expect(req1.state, KeyVerificationState.waitingSas);
|
expect(req1.state, KeyVerificationState.waitingSas);
|
||||||
|
|
||||||
// send mac
|
// send mac
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await req2.acceptSas();
|
await req2.acceptSas();
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.mac'));
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.done'));
|
||||||
evt = getLastSentEvent(req2);
|
evt = getLastSentEvent(req2);
|
||||||
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.done'));
|
||||||
|
|
||||||
expect(req1.state, KeyVerificationState.done);
|
expect(req1.state, KeyVerificationState.done);
|
||||||
expect(req2.state, KeyVerificationState.done);
|
expect(req2.state, KeyVerificationState.done);
|
||||||
|
|
@ -219,7 +245,6 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ask SSSS start', () async {
|
test('ask SSSS start', () async {
|
||||||
if (!olmEnabled) return;
|
|
||||||
client1.userDeviceKeys[client1.userID]!.masterKey!
|
client1.userDeviceKeys[client1.userID]!.masterKey!
|
||||||
.setDirectVerified(true);
|
.setDirectVerified(true);
|
||||||
await client1.encryption!.ssss.clearCache();
|
await client1.encryption!.ssss.clearCache();
|
||||||
|
|
@ -227,7 +252,8 @@ void main() {
|
||||||
.startVerification(newDirectChatEnableEncryption: false);
|
.startVerification(newDirectChatEnableEncryption: false);
|
||||||
expect(req1.state, KeyVerificationState.askSSSS);
|
expect(req1.state, KeyVerificationState.askSSSS);
|
||||||
await req1.openSSSS(recoveryKey: ssssKey);
|
await req1.openSSSS(recoveryKey: ssssKey);
|
||||||
await Future.delayed(Duration(seconds: 1));
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message'));
|
||||||
expect(req1.state, KeyVerificationState.waitingAccept);
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
||||||
|
|
||||||
await req1.cancel();
|
await req1.cancel();
|
||||||
|
|
@ -235,7 +261,6 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ask SSSS end', () async {
|
test('ask SSSS end', () async {
|
||||||
if (!olmEnabled) return;
|
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
// make sure our master key is *not* verified to not triger SSSS for now
|
// make sure our master key is *not* verified to not triger SSSS for now
|
||||||
client1.userDeviceKeys[client1.userID]!.masterKey!
|
client1.userDeviceKeys[client1.userID]!.masterKey!
|
||||||
|
|
@ -245,41 +270,53 @@ void main() {
|
||||||
.setDirectVerified(true);
|
.setDirectVerified(true);
|
||||||
final req1 = await client1.userDeviceKeys[client2.userID]!
|
final req1 = await client1.userDeviceKeys[client2.userID]!
|
||||||
.startVerification(newDirectChatEnableEncryption: false);
|
.startVerification(newDirectChatEnableEncryption: false);
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message'));
|
||||||
var evt = getLastSentEvent(req1);
|
var evt = getLastSentEvent(req1);
|
||||||
expect(req1.state, KeyVerificationState.waitingAccept);
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
||||||
|
|
||||||
late KeyVerification req2;
|
final comp = Completer<KeyVerification>();
|
||||||
final sub = client2.onKeyVerificationRequest.stream.listen((req) {
|
final sub = client2.onKeyVerificationRequest.stream.listen((req) {
|
||||||
req2 = req;
|
comp.complete(req);
|
||||||
});
|
});
|
||||||
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
await Future.delayed(Duration(milliseconds: 10));
|
final req2 = await comp.future;
|
||||||
await sub.cancel();
|
await sub.cancel();
|
||||||
|
|
||||||
// send ready
|
// send ready
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await req2.acceptVerification();
|
await req2.acceptVerification();
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.ready'));
|
||||||
evt = getLastSentEvent(req2);
|
evt = getLastSentEvent(req2);
|
||||||
expect(req2.state, KeyVerificationState.waitingAccept);
|
expect(req2.state, KeyVerificationState.waitingAccept);
|
||||||
|
|
||||||
// send start
|
// send start
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.start'));
|
||||||
evt = getLastSentEvent(req1);
|
evt = getLastSentEvent(req1);
|
||||||
|
|
||||||
// send accept
|
// send accept
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.accept'));
|
||||||
evt = getLastSentEvent(req2);
|
evt = getLastSentEvent(req2);
|
||||||
|
|
||||||
// send key
|
// send key
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.key'));
|
||||||
evt = getLastSentEvent(req1);
|
evt = getLastSentEvent(req1);
|
||||||
|
|
||||||
// send key
|
// send key
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.key'));
|
||||||
evt = getLastSentEvent(req2);
|
evt = getLastSentEvent(req2);
|
||||||
|
|
||||||
// receive last key
|
// receive last key
|
||||||
|
|
@ -313,6 +350,8 @@ void main() {
|
||||||
await req1.acceptSas();
|
await req1.acceptSas();
|
||||||
evt = getLastSentEvent(req1);
|
evt = getLastSentEvent(req1);
|
||||||
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.mac'));
|
||||||
expect(req1.state, KeyVerificationState.waitingSas);
|
expect(req1.state, KeyVerificationState.waitingSas);
|
||||||
|
|
||||||
// send mac
|
// send mac
|
||||||
|
|
@ -320,12 +359,16 @@ void main() {
|
||||||
await req2.acceptSas();
|
await req2.acceptSas();
|
||||||
evt = getLastSentEvent(req2);
|
evt = getLastSentEvent(req2);
|
||||||
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.mac'));
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.done'));
|
||||||
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
|
|
||||||
expect(req1.state, KeyVerificationState.askSSSS);
|
expect(req1.state, KeyVerificationState.askSSSS);
|
||||||
expect(req2.state, KeyVerificationState.done);
|
expect(req2.state, KeyVerificationState.done);
|
||||||
|
|
||||||
await req1.openSSSS(recoveryKey: ssssKey);
|
await req1.openSSSS(recoveryKey: ssssKey);
|
||||||
await Future.delayed(Duration(milliseconds: 10));
|
|
||||||
expect(req1.state, KeyVerificationState.done);
|
expect(req1.state, KeyVerificationState.done);
|
||||||
|
|
||||||
client1.encryption!.ssss = MockSSSS(client1.encryption!);
|
client1.encryption!.ssss = MockSSSS(client1.encryption!);
|
||||||
|
|
@ -334,34 +377,35 @@ void main() {
|
||||||
await req1.maybeRequestSSSSSecrets();
|
await req1.maybeRequestSSSSSecrets();
|
||||||
await Future.delayed(Duration(milliseconds: 10));
|
await Future.delayed(Duration(milliseconds: 10));
|
||||||
expect((client1.encryption!.ssss as MockSSSS).requestedSecrets, true);
|
expect((client1.encryption!.ssss as MockSSSS).requestedSecrets, true);
|
||||||
// delay for 12 seconds to be sure no other tests clear the ssss cache
|
|
||||||
await Future.delayed(Duration(seconds: 12));
|
|
||||||
|
|
||||||
await client1.encryption!.keyVerificationManager.cleanup();
|
await client1.encryption!.keyVerificationManager.cleanup();
|
||||||
await client2.encryption!.keyVerificationManager.cleanup();
|
await client2.encryption!.keyVerificationManager.cleanup();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('reject verification', () async {
|
test('reject verification', () async {
|
||||||
if (!olmEnabled) return;
|
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
// make sure our master key is *not* verified to not triger SSSS for now
|
// make sure our master key is *not* verified to not triger SSSS for now
|
||||||
client1.userDeviceKeys[client1.userID]!.masterKey!
|
client1.userDeviceKeys[client1.userID]!.masterKey!
|
||||||
.setDirectVerified(false);
|
.setDirectVerified(false);
|
||||||
final req1 = await client1.userDeviceKeys[client2.userID]!
|
final req1 = await client1.userDeviceKeys[client2.userID]!
|
||||||
.startVerification(newDirectChatEnableEncryption: false);
|
.startVerification(newDirectChatEnableEncryption: false);
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message'));
|
||||||
var evt = getLastSentEvent(req1);
|
var evt = getLastSentEvent(req1);
|
||||||
expect(req1.state, KeyVerificationState.waitingAccept);
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
||||||
|
|
||||||
late KeyVerification req2;
|
final comp = Completer<KeyVerification>();
|
||||||
final sub = client2.onKeyVerificationRequest.stream.listen((req) {
|
final sub = client2.onKeyVerificationRequest.stream.listen((req) {
|
||||||
req2 = req;
|
comp.complete(req);
|
||||||
});
|
});
|
||||||
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
await Future.delayed(Duration(milliseconds: 10));
|
final req2 = await comp.future;
|
||||||
await sub.cancel();
|
await sub.cancel();
|
||||||
|
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await req2.rejectVerification();
|
await req2.rejectVerification();
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.cancel'));
|
||||||
evt = getLastSentEvent(req2);
|
evt = getLastSentEvent(req2);
|
||||||
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
expect(req1.state, KeyVerificationState.error);
|
expect(req1.state, KeyVerificationState.error);
|
||||||
|
|
@ -372,48 +416,59 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('reject sas', () async {
|
test('reject sas', () async {
|
||||||
if (!olmEnabled) return;
|
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
// make sure our master key is *not* verified to not triger SSSS for now
|
// make sure our master key is *not* verified to not triger SSSS for now
|
||||||
client1.userDeviceKeys[client1.userID]!.masterKey!
|
client1.userDeviceKeys[client1.userID]!.masterKey!
|
||||||
.setDirectVerified(false);
|
.setDirectVerified(false);
|
||||||
final req1 = await client1.userDeviceKeys[client2.userID]!
|
final req1 = await client1.userDeviceKeys[client2.userID]!
|
||||||
.startVerification(newDirectChatEnableEncryption: false);
|
.startVerification(newDirectChatEnableEncryption: false);
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message'));
|
||||||
var evt = getLastSentEvent(req1);
|
var evt = getLastSentEvent(req1);
|
||||||
expect(req1.state, KeyVerificationState.waitingAccept);
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
||||||
|
|
||||||
late KeyVerification req2;
|
final comp = Completer<KeyVerification>();
|
||||||
final sub = client2.onKeyVerificationRequest.stream.listen((req) {
|
final sub = client2.onKeyVerificationRequest.stream.listen((req) {
|
||||||
req2 = req;
|
comp.complete(req);
|
||||||
});
|
});
|
||||||
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
await Future.delayed(Duration(milliseconds: 10));
|
final req2 = await comp.future;
|
||||||
await sub.cancel();
|
await sub.cancel();
|
||||||
|
|
||||||
// send ready
|
// send ready
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await req2.acceptVerification();
|
await req2.acceptVerification();
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.ready'));
|
||||||
evt = getLastSentEvent(req2);
|
evt = getLastSentEvent(req2);
|
||||||
expect(req2.state, KeyVerificationState.waitingAccept);
|
expect(req2.state, KeyVerificationState.waitingAccept);
|
||||||
|
|
||||||
// send start
|
// send start
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.start'));
|
||||||
evt = getLastSentEvent(req1);
|
evt = getLastSentEvent(req1);
|
||||||
|
|
||||||
// send accept
|
// send accept
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.accept'));
|
||||||
evt = getLastSentEvent(req2);
|
evt = getLastSentEvent(req2);
|
||||||
|
|
||||||
// send key
|
// send key
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.key'));
|
||||||
evt = getLastSentEvent(req1);
|
evt = getLastSentEvent(req1);
|
||||||
|
|
||||||
// send key
|
// send key
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.key'));
|
||||||
evt = getLastSentEvent(req2);
|
evt = getLastSentEvent(req2);
|
||||||
|
|
||||||
// receive last key
|
// receive last key
|
||||||
|
|
@ -421,8 +476,12 @@ void main() {
|
||||||
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
|
|
||||||
await req1.acceptSas();
|
await req1.acceptSas();
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.mac'));
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await req2.rejectSas();
|
await req2.rejectSas();
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.cancel'));
|
||||||
evt = getLastSentEvent(req2);
|
evt = getLastSentEvent(req2);
|
||||||
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
expect(req1.state, KeyVerificationState.error);
|
expect(req1.state, KeyVerificationState.error);
|
||||||
|
|
@ -433,22 +492,23 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('other device accepted', () async {
|
test('other device accepted', () async {
|
||||||
if (!olmEnabled) return;
|
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
// make sure our master key is *not* verified to not triger SSSS for now
|
// make sure our master key is *not* verified to not triger SSSS for now
|
||||||
client1.userDeviceKeys[client1.userID]!.masterKey!
|
client1.userDeviceKeys[client1.userID]!.masterKey!
|
||||||
.setDirectVerified(false);
|
.setDirectVerified(false);
|
||||||
final req1 = await client1.userDeviceKeys[client2.userID]!
|
final req1 = await client1.userDeviceKeys[client2.userID]!
|
||||||
.startVerification(newDirectChatEnableEncryption: false);
|
.startVerification(newDirectChatEnableEncryption: false);
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message'));
|
||||||
final evt = getLastSentEvent(req1);
|
final evt = getLastSentEvent(req1);
|
||||||
expect(req1.state, KeyVerificationState.waitingAccept);
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
||||||
|
|
||||||
late KeyVerification req2;
|
final comp = Completer<KeyVerification>();
|
||||||
final sub = client2.onKeyVerificationRequest.stream.listen((req) {
|
final sub = client2.onKeyVerificationRequest.stream.listen((req) {
|
||||||
req2 = req;
|
comp.complete(req);
|
||||||
});
|
});
|
||||||
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
||||||
await Future.delayed(Duration(milliseconds: 10));
|
final req2 = await comp.future;
|
||||||
await sub.cancel();
|
await sub.cancel();
|
||||||
|
|
||||||
await client2.encryption!.keyVerificationManager
|
await client2.encryption!.keyVerificationManager
|
||||||
|
|
@ -473,14 +533,10 @@ void main() {
|
||||||
expect(req2.state, KeyVerificationState.error);
|
expect(req2.state, KeyVerificationState.error);
|
||||||
|
|
||||||
await req2.cancel();
|
await req2.cancel();
|
||||||
|
await FakeMatrixApi.firstWhere((e) => e.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.cancel'));
|
||||||
await client1.encryption!.keyVerificationManager.cleanup();
|
await client1.encryption!.keyVerificationManager.cleanup();
|
||||||
await client2.encryption!.keyVerificationManager.cleanup();
|
await client2.encryption!.keyVerificationManager.cleanup();
|
||||||
});
|
});
|
||||||
|
}, skip: skip);
|
||||||
test('dispose client', () async {
|
|
||||||
if (!olmEnabled) return;
|
|
||||||
await client1.dispose(closeDatabase: true);
|
|
||||||
await client2.dispose(closeDatabase: true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ void main() {
|
||||||
|
|
||||||
late Client client;
|
late Client client;
|
||||||
|
|
||||||
test('setupClient', () async {
|
setUp(() async {
|
||||||
try {
|
try {
|
||||||
await olm.init();
|
await olm.init();
|
||||||
olm.get_library_version();
|
olm.get_library_version();
|
||||||
|
|
@ -42,9 +42,10 @@ void main() {
|
||||||
Logs().w('[LibOlm] Failed to load LibOlm', e);
|
Logs().w('[LibOlm] Failed to load LibOlm', e);
|
||||||
}
|
}
|
||||||
Logs().i('[LibOlm] Enabled: $olmEnabled');
|
Logs().i('[LibOlm] Enabled: $olmEnabled');
|
||||||
if (!olmEnabled) return;
|
if (!olmEnabled) return Future.value();
|
||||||
|
|
||||||
client = await getClient();
|
client = await getClient();
|
||||||
|
return Future.value();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('signatures', () async {
|
test('signatures', () async {
|
||||||
|
|
@ -89,10 +90,11 @@ void main() {
|
||||||
|
|
||||||
test('handleDeviceOneTimeKeysCount', () async {
|
test('handleDeviceOneTimeKeysCount', () async {
|
||||||
if (!olmEnabled) return;
|
if (!olmEnabled) return;
|
||||||
|
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
client.encryption!.olmManager
|
client.encryption!.olmManager
|
||||||
.handleDeviceOneTimeKeysCount({'signed_curve25519': 20}, null);
|
.handleDeviceOneTimeKeysCount({'signed_curve25519': 20}, null);
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await FakeMatrixApi.firstWhereValue('/client/v3/keys/upload');
|
||||||
expect(
|
expect(
|
||||||
FakeMatrixApi.calledEndpoints.containsKey('/client/v3/keys/upload'),
|
FakeMatrixApi.calledEndpoints.containsKey('/client/v3/keys/upload'),
|
||||||
true);
|
true);
|
||||||
|
|
@ -100,14 +102,15 @@ void main() {
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
client.encryption!.olmManager
|
client.encryption!.olmManager
|
||||||
.handleDeviceOneTimeKeysCount({'signed_curve25519': 70}, null);
|
.handleDeviceOneTimeKeysCount({'signed_curve25519': 70}, null);
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await FakeMatrixApi.firstWhereValue('/client/v3/keys/upload')
|
||||||
|
.timeout(Duration(milliseconds: 50), onTimeout: () => '');
|
||||||
expect(
|
expect(
|
||||||
FakeMatrixApi.calledEndpoints.containsKey('/client/v3/keys/upload'),
|
FakeMatrixApi.calledEndpoints.containsKey('/client/v3/keys/upload'),
|
||||||
false);
|
false);
|
||||||
|
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
client.encryption!.olmManager.handleDeviceOneTimeKeysCount(null, []);
|
client.encryption!.olmManager.handleDeviceOneTimeKeysCount(null, []);
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await FakeMatrixApi.firstWhereValue('/client/v3/keys/upload');
|
||||||
expect(
|
expect(
|
||||||
FakeMatrixApi.calledEndpoints.containsKey('/client/v3/keys/upload'),
|
FakeMatrixApi.calledEndpoints.containsKey('/client/v3/keys/upload'),
|
||||||
true);
|
true);
|
||||||
|
|
@ -116,7 +119,7 @@ void main() {
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
client.encryption!.olmManager
|
client.encryption!.olmManager
|
||||||
.handleDeviceOneTimeKeysCount(null, ['signed_curve25519']);
|
.handleDeviceOneTimeKeysCount(null, ['signed_curve25519']);
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await FakeMatrixApi.firstWhereValue('/client/v3/keys/upload');
|
||||||
expect(
|
expect(
|
||||||
FakeMatrixApi.calledEndpoints.containsKey('/client/v3/keys/upload'),
|
FakeMatrixApi.calledEndpoints.containsKey('/client/v3/keys/upload'),
|
||||||
true);
|
true);
|
||||||
|
|
|
||||||
|
|
@ -93,13 +93,14 @@ void main() {
|
||||||
'sender_claimed_ed25519_key': client.fingerprintKey,
|
'sender_claimed_ed25519_key': client.fingerprintKey,
|
||||||
};
|
};
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
client.encryption!.keyManager.setInboundGroupSession(
|
await client.encryption!.keyManager.setInboundGroupSession(
|
||||||
roomId, sessionId, senderKey, sessionPayload,
|
roomId, sessionId, senderKey, sessionPayload,
|
||||||
forwarded: true);
|
forwarded: true);
|
||||||
await Future.delayed(Duration(milliseconds: 500));
|
|
||||||
var dbSessions = await client.database!.getInboundGroupSessionsToUpload();
|
var dbSessions = await client.database!.getInboundGroupSessionsToUpload();
|
||||||
expect(dbSessions.isNotEmpty, true);
|
expect(dbSessions.isNotEmpty, true);
|
||||||
await client.encryption!.keyManager.backgroundTasks();
|
await client.encryption!.keyManager.backgroundTasks();
|
||||||
|
await FakeMatrixApi.firstWhereValue(
|
||||||
|
'/client/v3/room_keys/keys?version=5');
|
||||||
final payload = FakeMatrixApi
|
final payload = FakeMatrixApi
|
||||||
.calledEndpoints['/client/v3/room_keys/keys?version=5']!.first;
|
.calledEndpoints['/client/v3/room_keys/keys?version=5']!.first;
|
||||||
dbSessions = await client.database!.getInboundGroupSessionsToUpload();
|
dbSessions = await client.database!.getInboundGroupSessionsToUpload();
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,12 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:core';
|
import 'dart:core';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:http/testing.dart';
|
|
||||||
import 'package:matrix/matrix.dart' as sdk;
|
import 'package:matrix/matrix.dart' as sdk;
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
|
|
@ -35,120 +35,209 @@ Map<String, dynamic> decodeJson(dynamic data) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
class FakeMatrixApi extends MockClient {
|
class FakeMatrixApi extends BaseClient {
|
||||||
static final calledEndpoints = <String, List<dynamic>>{};
|
static Map<String, List<dynamic>> get calledEndpoints =>
|
||||||
static int eventCounter = 0;
|
currentApi!._calledEndpoints;
|
||||||
static sdk.Client? client;
|
static int get eventCounter => currentApi!._eventCounter;
|
||||||
static bool failToDevice = false;
|
static set eventCounter(int c) {
|
||||||
|
currentApi!._eventCounter = c;
|
||||||
|
}
|
||||||
|
|
||||||
FakeMatrixApi()
|
static set client(sdk.Client? c) {
|
||||||
: super((request) async {
|
currentApi?._client = c;
|
||||||
// Collect data from Request
|
}
|
||||||
var action = request.url.path;
|
|
||||||
if (request.url.path.contains('/_matrix')) {
|
|
||||||
action = request.url.path.split('/_matrix').last +
|
|
||||||
'?' +
|
|
||||||
request.url.query;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action.endsWith('?')) {
|
static set failToDevice(bool fail) {
|
||||||
action = action.substring(0, action.length - 1);
|
currentApi?._failToDevice = fail;
|
||||||
}
|
}
|
||||||
if (action.endsWith('?server_name')) {
|
|
||||||
// This can be removed after matrix_api_lite is released with:
|
|
||||||
// https://gitlab.com/famedly/libraries/matrix_api_lite/-/merge_requests/16
|
|
||||||
action = action.substring(0, action.length - 12);
|
|
||||||
}
|
|
||||||
if (action.endsWith('/')) {
|
|
||||||
action = action.substring(0, action.length - 1);
|
|
||||||
}
|
|
||||||
final method = request.method;
|
|
||||||
final dynamic data =
|
|
||||||
method == 'GET' ? request.url.queryParameters : request.body;
|
|
||||||
dynamic res = {};
|
|
||||||
var statusCode = 200;
|
|
||||||
|
|
||||||
//print('$method request to $action with Data: $data');
|
static set trace(bool t) {
|
||||||
|
currentApi?._trace = t;
|
||||||
|
}
|
||||||
|
|
||||||
// Sync requests with timeout
|
final _calledEndpoints = <String, List<dynamic>>{};
|
||||||
if (data is Map<String, dynamic> && data['timeout'] is String) {
|
int _eventCounter = 0;
|
||||||
await Future.delayed(Duration(seconds: 5));
|
sdk.Client? _client;
|
||||||
}
|
bool _failToDevice = false;
|
||||||
|
bool _trace = false;
|
||||||
|
final _apiCallStream = StreamController<String>.broadcast();
|
||||||
|
|
||||||
if (request.url.origin != 'https://fakeserver.notexisting') {
|
static FakeMatrixApi? currentApi;
|
||||||
return Response(
|
|
||||||
'<html><head></head><body>Not found...</body></html>', 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call API
|
static Future<String> firstWhereValue(String value) {
|
||||||
if (!calledEndpoints.containsKey(action)) {
|
return firstWhere((v) => v == value);
|
||||||
calledEndpoints[action] = <dynamic>[];
|
}
|
||||||
}
|
|
||||||
calledEndpoints[action]?.add(data);
|
|
||||||
final act = api[method]?[action];
|
|
||||||
if (act != null) {
|
|
||||||
res = act(data);
|
|
||||||
if (res is Map && res.containsKey('errcode')) {
|
|
||||||
if (res['errcode'] == 'M_NOT_FOUND') {
|
|
||||||
statusCode = 404;
|
|
||||||
} else {
|
|
||||||
statusCode = 405;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (method == 'PUT' &&
|
|
||||||
action.contains('/client/v3/sendToDevice/')) {
|
|
||||||
res = {};
|
|
||||||
if (failToDevice) {
|
|
||||||
statusCode = 500;
|
|
||||||
}
|
|
||||||
} else if (method == 'GET' &&
|
|
||||||
action.contains('/client/v3/rooms/') &&
|
|
||||||
action.contains('/state/m.room.member/') &&
|
|
||||||
!action.endsWith('%40alicyy%3Aexample.com')) {
|
|
||||||
res = {'displayname': ''};
|
|
||||||
} else if (method == 'PUT' &&
|
|
||||||
action.contains(
|
|
||||||
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/')) {
|
|
||||||
res = {'event_id': '\$event${FakeMatrixApi.eventCounter++}'};
|
|
||||||
} else if (method == 'PUT' &&
|
|
||||||
action.contains(
|
|
||||||
'/client/v3/rooms/!1234%3AfakeServer.notExisting/state/')) {
|
|
||||||
res = {'event_id': '\$event${FakeMatrixApi.eventCounter++}'};
|
|
||||||
} else if (action.contains('/client/v3/sync')) {
|
|
||||||
res = {
|
|
||||||
'next_batch': DateTime.now().millisecondsSinceEpoch.toString(),
|
|
||||||
};
|
|
||||||
} else if (method == 'PUT' &&
|
|
||||||
client != null &&
|
|
||||||
action.contains('/account_data/') &&
|
|
||||||
!action.contains('/room/')) {
|
|
||||||
final type = Uri.decodeComponent(action.split('/').last);
|
|
||||||
final syncUpdate = sdk.SyncUpdate(
|
|
||||||
nextBatch: '',
|
|
||||||
accountData: [
|
|
||||||
sdk.BasicEvent(content: decodeJson(data), type: type)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
if (client?.database != null) {
|
|
||||||
await client?.database?.transaction(() async {
|
|
||||||
await client?.handleSync(syncUpdate);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await client?.handleSync(syncUpdate);
|
|
||||||
}
|
|
||||||
res = {};
|
|
||||||
} else {
|
|
||||||
res = {
|
|
||||||
'errcode': 'M_UNRECOGNIZED',
|
|
||||||
'error': 'Unrecognized request'
|
|
||||||
};
|
|
||||||
statusCode = 405;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Response.bytes(utf8.encode(json.encode(res)), statusCode);
|
static Future<String> firstWhere(bool Function(String element) test) {
|
||||||
|
for (final e in currentApi!._calledEndpoints.entries) {
|
||||||
|
if (e.value.isNotEmpty && test(e.key)) {
|
||||||
|
return Future.value(e.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final completer = Completer<String>();
|
||||||
|
StreamSubscription<String>? sub;
|
||||||
|
sub = currentApi!._apiCallStream.stream.listen((action) {
|
||||||
|
if (test(action)) {
|
||||||
|
sub?.cancel();
|
||||||
|
completer.complete(action);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureOr<Response> mockIntercept(Request request) async {
|
||||||
|
// Collect data from Request
|
||||||
|
var action = request.url.path;
|
||||||
|
if (request.url.path.contains('/_matrix')) {
|
||||||
|
action =
|
||||||
|
request.url.path.split('/_matrix').last + '?' + request.url.query;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore: avoid_print
|
||||||
|
if (_trace) print('called $action');
|
||||||
|
|
||||||
|
if (action.endsWith('?')) {
|
||||||
|
action = action.substring(0, action.length - 1);
|
||||||
|
}
|
||||||
|
if (action.endsWith('?server_name')) {
|
||||||
|
// This can be removed after matrix_api_lite is released with:
|
||||||
|
// https://gitlab.com/famedly/libraries/matrix_api_lite/-/merge_requests/16
|
||||||
|
action = action.substring(0, action.length - 12);
|
||||||
|
}
|
||||||
|
if (action.endsWith('/')) {
|
||||||
|
action = action.substring(0, action.length - 1);
|
||||||
|
}
|
||||||
|
final method = request.method;
|
||||||
|
final dynamic data =
|
||||||
|
method == 'GET' ? request.url.queryParameters : request.body;
|
||||||
|
dynamic res = {};
|
||||||
|
var statusCode = 200;
|
||||||
|
|
||||||
|
//print('\$method request to $action with Data: $data');
|
||||||
|
|
||||||
|
// Sync requests with timeout
|
||||||
|
if (data is Map<String, dynamic> && data['timeout'] is String) {
|
||||||
|
await Future.delayed(Duration(seconds: 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.url.origin != 'https://fakeserver.notexisting') {
|
||||||
|
return Response(
|
||||||
|
'<html><head></head><body>Not found...</body></html>', 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call API
|
||||||
|
(_calledEndpoints[action] ??= <dynamic>[]).add(data);
|
||||||
|
final act = api[method]?[action];
|
||||||
|
if (act != null) {
|
||||||
|
res = act(data);
|
||||||
|
if (res is Map && res.containsKey('errcode')) {
|
||||||
|
if (res['errcode'] == 'M_NOT_FOUND') {
|
||||||
|
statusCode = 404;
|
||||||
|
} else {
|
||||||
|
statusCode = 405;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (method == 'PUT' && action.contains('/client/v3/sendToDevice/')) {
|
||||||
|
res = {};
|
||||||
|
if (_failToDevice) {
|
||||||
|
statusCode = 500;
|
||||||
|
}
|
||||||
|
} else if (method == 'GET' &&
|
||||||
|
action.contains('/client/v3/rooms/') &&
|
||||||
|
action.contains('/state/m.room.member/') &&
|
||||||
|
!action.endsWith('%40alicyy%3Aexample.com')) {
|
||||||
|
res = {'displayname': ''};
|
||||||
|
} else if (method == 'PUT' &&
|
||||||
|
action.contains(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/')) {
|
||||||
|
res = {'event_id': '\$event${_eventCounter++}'};
|
||||||
|
} else if (method == 'PUT' &&
|
||||||
|
action.contains(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/state/')) {
|
||||||
|
res = {'event_id': '\$event${_eventCounter++}'};
|
||||||
|
} else if (action.contains('/client/v3/sync')) {
|
||||||
|
res = {
|
||||||
|
'next_batch': DateTime.now().millisecondsSinceEpoch.toString(),
|
||||||
|
};
|
||||||
|
} else if (method == 'PUT' &&
|
||||||
|
_client != null &&
|
||||||
|
action.contains('/account_data/') &&
|
||||||
|
!action.contains('/room/')) {
|
||||||
|
final type = Uri.decodeComponent(action.split('/').last);
|
||||||
|
final syncUpdate = sdk.SyncUpdate(
|
||||||
|
nextBatch: '',
|
||||||
|
accountData: [sdk.BasicEvent(content: decodeJson(data), type: type)],
|
||||||
|
);
|
||||||
|
if (_client?.database != null) {
|
||||||
|
await _client?.database?.transaction(() async {
|
||||||
|
await _client?.handleSync(syncUpdate);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
await _client?.handleSync(syncUpdate);
|
||||||
|
}
|
||||||
|
res = {};
|
||||||
|
} else {
|
||||||
|
res = {'errcode': 'M_UNRECOGNIZED', 'error': 'Unrecognized request'};
|
||||||
|
statusCode = 405;
|
||||||
|
}
|
||||||
|
|
||||||
static Map<String, dynamic> messagesResponsePast = {
|
unawaited(Future.delayed(Duration(milliseconds: 1)).then((_) async {
|
||||||
|
_apiCallStream.add(action);
|
||||||
|
}));
|
||||||
|
return Response.bytes(utf8.encode(json.encode(res)), statusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<StreamedResponse> send(BaseRequest baseRequest) async {
|
||||||
|
final bodyStream = baseRequest.finalize();
|
||||||
|
final bodyBytes = await bodyStream.toBytes();
|
||||||
|
final request = Request(baseRequest.method, baseRequest.url)
|
||||||
|
..persistentConnection = baseRequest.persistentConnection
|
||||||
|
..followRedirects = baseRequest.followRedirects
|
||||||
|
..maxRedirects = baseRequest.maxRedirects
|
||||||
|
..headers.addAll(baseRequest.headers)
|
||||||
|
..bodyBytes = bodyBytes
|
||||||
|
..finalize();
|
||||||
|
|
||||||
|
final response = await mockIntercept(request);
|
||||||
|
return StreamedResponse(
|
||||||
|
ByteStream.fromBytes(response.bodyBytes), response.statusCode,
|
||||||
|
contentLength: response.contentLength,
|
||||||
|
request: baseRequest,
|
||||||
|
headers: response.headers,
|
||||||
|
isRedirect: response.isRedirect,
|
||||||
|
persistentConnection: response.persistentConnection,
|
||||||
|
reasonPhrase: response.reasonPhrase);
|
||||||
|
}
|
||||||
|
|
||||||
|
FakeMatrixApi() {
|
||||||
|
currentApi = this;
|
||||||
|
api['POST']?['/client/v3/keys/device_signing/upload'] = (var reqI) {
|
||||||
|
if (_client != null) {
|
||||||
|
final jsonBody = decodeJson(reqI);
|
||||||
|
for (final keyType in {
|
||||||
|
'master_key',
|
||||||
|
'self_signing_key',
|
||||||
|
'user_signing_key'
|
||||||
|
}) {
|
||||||
|
if (jsonBody[keyType] != null) {
|
||||||
|
final key =
|
||||||
|
sdk.CrossSigningKey.fromJson(jsonBody[keyType], _client!);
|
||||||
|
_client!.userDeviceKeys[_client!.userID!]?.crossSigningKeys
|
||||||
|
.removeWhere((k, v) => v.usage.contains(key.usage.first));
|
||||||
|
_client!.userDeviceKeys[_client!.userID!]
|
||||||
|
?.crossSigningKeys[key.publicKey!] = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// and generate a fake sync
|
||||||
|
_client!.handleSync(sdk.SyncUpdate(nextBatch: ''));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Map<String, dynamic> messagesResponsePast = {
|
||||||
'start': 't47429-4392820_219380_26003_2265',
|
'start': 't47429-4392820_219380_26003_2265',
|
||||||
'end': 't47409-4357353_219380_26003_2265',
|
'end': 't47409-4357353_219380_26003_2265',
|
||||||
'chunk': [
|
'chunk': [
|
||||||
|
|
@ -206,7 +295,7 @@ class FakeMatrixApi extends MockClient {
|
||||||
],
|
],
|
||||||
'state': [],
|
'state': [],
|
||||||
};
|
};
|
||||||
static Map<String, dynamic> messagesResponseFuture = {
|
static const Map<String, dynamic> messagesResponseFuture = {
|
||||||
'start': 't456',
|
'start': 't456',
|
||||||
'end': 't789',
|
'end': 't789',
|
||||||
'chunk': [
|
'chunk': [
|
||||||
|
|
@ -264,7 +353,7 @@ class FakeMatrixApi extends MockClient {
|
||||||
],
|
],
|
||||||
'state': [],
|
'state': [],
|
||||||
};
|
};
|
||||||
static Map<String, dynamic> messagesResponseFutureEnd = {
|
static const Map<String, dynamic> messagesResponseFutureEnd = {
|
||||||
'start': 't789',
|
'start': 't789',
|
||||||
'end': null,
|
'end': null,
|
||||||
'chunk': [],
|
'chunk': [],
|
||||||
|
|
@ -856,7 +945,7 @@ class FakeMatrixApi extends MockClient {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static final Map<String, Map<String, dynamic>> api = {
|
final Map<String, Map<String, dynamic>> api = {
|
||||||
'GET': {
|
'GET': {
|
||||||
'/path/to/auth/error': (var req) => {
|
'/path/to/auth/error': (var req) => {
|
||||||
'errcode': 'M_FORBIDDEN',
|
'errcode': 'M_FORBIDDEN',
|
||||||
|
|
@ -2177,28 +2266,6 @@ class FakeMatrixApi extends MockClient {
|
||||||
'/client/v3/rooms/!localpart%3Aserver.abc/ban': (var reqI) => {},
|
'/client/v3/rooms/!localpart%3Aserver.abc/ban': (var reqI) => {},
|
||||||
'/client/v3/rooms/!localpart%3Aserver.abc/unban': (var reqI) => {},
|
'/client/v3/rooms/!localpart%3Aserver.abc/unban': (var reqI) => {},
|
||||||
'/client/v3/rooms/!localpart%3Aserver.abc/invite': (var reqI) => {},
|
'/client/v3/rooms/!localpart%3Aserver.abc/invite': (var reqI) => {},
|
||||||
'/client/v3/keys/device_signing/upload': (var reqI) {
|
|
||||||
if (client != null) {
|
|
||||||
final jsonBody = decodeJson(reqI);
|
|
||||||
for (final keyType in {
|
|
||||||
'master_key',
|
|
||||||
'self_signing_key',
|
|
||||||
'user_signing_key'
|
|
||||||
}) {
|
|
||||||
if (jsonBody[keyType] != null) {
|
|
||||||
final key =
|
|
||||||
sdk.CrossSigningKey.fromJson(jsonBody[keyType], client!);
|
|
||||||
client!.userDeviceKeys[client!.userID!]?.crossSigningKeys
|
|
||||||
.removeWhere((k, v) => v.usage.contains(key.usage.first));
|
|
||||||
client!.userDeviceKeys[client!.userID!]
|
|
||||||
?.crossSigningKeys[key.publicKey!] = key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// and generate a fake sync
|
|
||||||
client!.handleSync(sdk.SyncUpdate(nextBatch: ''));
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
'/client/v3/keys/signatures/upload': (var reqI) => {'failures': {}},
|
'/client/v3/keys/signatures/upload': (var reqI) => {'failures': {}},
|
||||||
'/client/v3/room_keys/version': (var reqI) => {'version': '5'},
|
'/client/v3/room_keys/version': (var reqI) => {'version': '5'},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -16,28 +16,55 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
import 'package:matrix/src/models/timeline_chunk.dart';
|
import 'package:matrix/src/models/timeline_chunk.dart';
|
||||||
|
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'package:olm/olm.dart' as olm;
|
import 'package:olm/olm.dart' as olm;
|
||||||
import 'fake_client.dart';
|
import 'fake_client.dart';
|
||||||
|
import 'fake_matrix_api.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('Timeline context', () {
|
group('Timeline context', () {
|
||||||
Logs().level = Level.error;
|
Logs().level = Level.error;
|
||||||
final roomID = '!1234:example.com';
|
final roomID = '!1234:example.com';
|
||||||
final testTimeStamp = DateTime.now().millisecondsSinceEpoch;
|
var testTimeStamp = 0;
|
||||||
var updateCount = 0;
|
var updateCount = 0;
|
||||||
final insertList = <int>[];
|
final insertList = <int>[];
|
||||||
final changeList = <int>[];
|
final changeList = <int>[];
|
||||||
final removeList = <int>[];
|
final removeList = <int>[];
|
||||||
var olmEnabled = true;
|
var olmEnabled = true;
|
||||||
|
|
||||||
|
final countStream = StreamController<int>.broadcast();
|
||||||
|
Future<int> waitForCount(int count) {
|
||||||
|
if (updateCount == count) {
|
||||||
|
return Future.value(updateCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
final completer = Completer<int>();
|
||||||
|
|
||||||
|
StreamSubscription<int>? sub;
|
||||||
|
sub = countStream.stream.listen((newCount) {
|
||||||
|
if (newCount == count) {
|
||||||
|
sub?.cancel();
|
||||||
|
completer.complete(count);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return completer.future.timeout(Duration(seconds: 1),
|
||||||
|
onTimeout: () async {
|
||||||
|
throw TimeoutException(
|
||||||
|
'Failed to wait for updateCount == $count, current == $updateCount',
|
||||||
|
Duration(seconds: 1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
late Client client;
|
late Client client;
|
||||||
late Room room;
|
late Room room;
|
||||||
late Timeline timeline;
|
late Timeline timeline;
|
||||||
test('create stuff', () async {
|
setUp(() async {
|
||||||
try {
|
try {
|
||||||
await olm.init();
|
await olm.init();
|
||||||
olm.get_library_version();
|
olm.get_library_version();
|
||||||
|
|
@ -56,21 +83,33 @@ void main() {
|
||||||
chunk: TimelineChunk(events: [], nextBatch: 't456', prevBatch: 't123'),
|
chunk: TimelineChunk(events: [], nextBatch: 't456', prevBatch: 't123'),
|
||||||
onUpdate: () {
|
onUpdate: () {
|
||||||
updateCount++;
|
updateCount++;
|
||||||
|
countStream.add(updateCount);
|
||||||
},
|
},
|
||||||
onInsert: insertList.add,
|
onInsert: insertList.add,
|
||||||
onChange: changeList.add,
|
onChange: changeList.add,
|
||||||
onRemove: removeList.add,
|
onRemove: removeList.add,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(timeline.isFragmentedTimeline, true);
|
expect(timeline.isFragmentedTimeline, true);
|
||||||
expect(timeline.allowNewEvent, false);
|
expect(timeline.allowNewEvent, false);
|
||||||
|
updateCount = 0;
|
||||||
|
insertList.clear();
|
||||||
|
changeList.clear();
|
||||||
|
removeList.clear();
|
||||||
|
|
||||||
|
await client.abortSync();
|
||||||
|
testTimeStamp = DateTime.now().millisecondsSinceEpoch;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
tearDown(() => client.dispose(closeDatabase: true).onError((e, s) {}));
|
||||||
|
|
||||||
test('Request future', () async {
|
test('Request future', () async {
|
||||||
timeline.events.clear();
|
timeline.events.clear();
|
||||||
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
|
|
||||||
await timeline.requestFuture();
|
await timeline.requestFuture();
|
||||||
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await FakeMatrixApi.firstWhere((a) => a.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3Aexample.com/messages?from=t456&dir=f'));
|
||||||
|
|
||||||
expect(updateCount, 3);
|
expect(updateCount, 3);
|
||||||
expect(insertList, [0, 1, 2]);
|
expect(insertList, [0, 1, 2]);
|
||||||
|
|
@ -86,13 +125,12 @@ void main() {
|
||||||
|
|
||||||
/// We send a message in a fragmented timeline, it didn't reached the end so we shouldn't be displayed.
|
/// We send a message in a fragmented timeline, it didn't reached the end so we shouldn't be displayed.
|
||||||
test('Send message not displayed', () async {
|
test('Send message not displayed', () async {
|
||||||
updateCount = 0;
|
|
||||||
|
|
||||||
await room.sendTextEvent('test', txid: '1234');
|
await room.sendTextEvent('test', txid: '1234');
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await FakeMatrixApi.firstWhere((a) => a.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3Aexample.com/send/m.room.message/1234'));
|
||||||
|
|
||||||
expect(updateCount, 0);
|
expect(updateCount, 0);
|
||||||
expect(insertList, [0, 1, 2]);
|
expect(insertList, []);
|
||||||
expect(insertList.length,
|
expect(insertList.length,
|
||||||
timeline.events.length); // expect no new events to have been added
|
timeline.events.length); // expect no new events to have been added
|
||||||
|
|
||||||
|
|
@ -111,20 +149,21 @@ void main() {
|
||||||
},
|
},
|
||||||
)); // just assume that it was on the server for this call but not for the following.
|
)); // just assume that it was on the server for this call but not for the following.
|
||||||
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
|
||||||
|
|
||||||
expect(updateCount, 0);
|
expect(updateCount, 0);
|
||||||
expect(insertList, [0, 1, 2]);
|
expect(insertList, []);
|
||||||
expect(timeline.events.length,
|
expect(timeline.events.length,
|
||||||
3); // we still expect the timeline to contain the same numbre of elements
|
0); // we still expect the timeline to contain the same numbre of elements
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Request future end of timeline', () async {
|
test('Request future end of timeline', () async {
|
||||||
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
|
await timeline.requestFuture();
|
||||||
await timeline.requestFuture();
|
await timeline.requestFuture();
|
||||||
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await FakeMatrixApi.firstWhere((a) => a.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3Aexample.com/messages?from=t789&dir=f'));
|
||||||
|
|
||||||
expect(updateCount, 3);
|
expect(updateCount, 6);
|
||||||
expect(insertList, [0, 1, 2]);
|
expect(insertList, [0, 1, 2]);
|
||||||
expect(insertList.length, timeline.events.length);
|
expect(insertList.length, timeline.events.length);
|
||||||
expect(timeline.events[0].eventId, '3143273582443PhrSn:example.org');
|
expect(timeline.events[0].eventId, '3143273582443PhrSn:example.org');
|
||||||
|
|
@ -137,10 +176,15 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Send message', () async {
|
test('Send message', () async {
|
||||||
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
|
await timeline.requestFuture();
|
||||||
|
await timeline.requestFuture();
|
||||||
await room.sendTextEvent('test', txid: '1234');
|
await room.sendTextEvent('test', txid: '1234');
|
||||||
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await FakeMatrixApi.firstWhere((a) => a.startsWith(
|
||||||
expect(updateCount, 5);
|
'/client/v3/rooms/!1234%3Aexample.com/send/m.room.message/1234'));
|
||||||
|
|
||||||
|
expect(updateCount, 8);
|
||||||
expect(insertList, [0, 1, 2, 0]);
|
expect(insertList, [0, 1, 2, 0]);
|
||||||
expect(insertList.length, timeline.events.length);
|
expect(insertList.length, timeline.events.length);
|
||||||
final eventId = timeline.events[0].eventId;
|
final eventId = timeline.events[0].eventId;
|
||||||
|
|
@ -161,9 +205,9 @@ void main() {
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(9);
|
||||||
|
|
||||||
expect(updateCount, 6);
|
expect(updateCount, 9);
|
||||||
expect(insertList, [0, 1, 2, 0]);
|
expect(insertList, [0, 1, 2, 0]);
|
||||||
expect(insertList.length, timeline.events.length);
|
expect(insertList.length, timeline.events.length);
|
||||||
expect(timeline.events[0].eventId, eventId);
|
expect(timeline.events[0].eventId, eventId);
|
||||||
|
|
@ -171,7 +215,10 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Send message with error', () async {
|
test('Send message with error', () async {
|
||||||
updateCount = 0;
|
await timeline.requestFuture();
|
||||||
|
await timeline.requestFuture();
|
||||||
|
await waitForCount(6);
|
||||||
|
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
|
|
@ -184,22 +231,31 @@ void main() {
|
||||||
'origin_server_ts': testTimeStamp
|
'origin_server_ts': testTimeStamp
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
|
||||||
|
|
||||||
expect(updateCount, 1);
|
await waitForCount(7);
|
||||||
await room.sendTextEvent('test', txid: 'errortxid');
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
|
||||||
|
|
||||||
expect(updateCount, 3);
|
|
||||||
await room.sendTextEvent('test', txid: 'errortxid2');
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
|
||||||
await room.sendTextEvent('test', txid: 'errortxid3');
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
|
||||||
|
|
||||||
expect(updateCount, 7);
|
expect(updateCount, 7);
|
||||||
expect(insertList, [0, 1, 2, 0, 0, 0, 1, 2]);
|
|
||||||
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
|
|
||||||
|
await room.sendTextEvent('test', txid: 'errortxid');
|
||||||
|
|
||||||
|
await FakeMatrixApi.firstWhere((a) => a.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3Aexample.com/send/m.room.message/errortxid'));
|
||||||
|
|
||||||
|
await waitForCount(9);
|
||||||
|
expect(updateCount, 9);
|
||||||
|
await room.sendTextEvent('test', txid: 'errortxid2');
|
||||||
|
await FakeMatrixApi.firstWhere((a) => a.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3Aexample.com/send/m.room.message/errortxid2'));
|
||||||
|
await room.sendTextEvent('test', txid: 'errortxid3');
|
||||||
|
await FakeMatrixApi.firstWhere((a) => a.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3Aexample.com/send/m.room.message/errortxid3'));
|
||||||
|
|
||||||
|
expect(updateCount, 13);
|
||||||
|
expect(insertList, [0, 1, 2, 0, 0, 1, 2]);
|
||||||
expect(insertList.length, timeline.events.length);
|
expect(insertList.length, timeline.events.length);
|
||||||
expect(changeList, [0, 0, 0, 1, 2]);
|
expect(changeList, [0, 1, 2]);
|
||||||
expect(removeList, []);
|
expect(removeList, []);
|
||||||
expect(timeline.events[0].status, EventStatus.error);
|
expect(timeline.events[0].status, EventStatus.error);
|
||||||
expect(timeline.events[1].status, EventStatus.error);
|
expect(timeline.events[1].status, EventStatus.error);
|
||||||
|
|
@ -207,21 +263,51 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Remove message', () async {
|
test('Remove message', () async {
|
||||||
updateCount = 0;
|
await timeline.requestFuture();
|
||||||
|
await timeline.requestFuture();
|
||||||
|
// send a failed message
|
||||||
|
client.onEvent.add(EventUpdate(
|
||||||
|
type: EventUpdateType.timeline,
|
||||||
|
roomID: roomID,
|
||||||
|
content: {
|
||||||
|
'type': 'm.room.message',
|
||||||
|
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||||
|
'sender': '@alice:example.com',
|
||||||
|
'status': EventStatus.sending.intValue,
|
||||||
|
'event_id': 'abc',
|
||||||
|
'origin_server_ts': testTimeStamp
|
||||||
|
},
|
||||||
|
));
|
||||||
|
await waitForCount(7);
|
||||||
|
|
||||||
await timeline.events[0].remove();
|
await timeline.events[0].remove();
|
||||||
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(8);
|
||||||
|
expect(updateCount, 8);
|
||||||
|
|
||||||
expect(updateCount, 1);
|
expect(insertList, [0, 1, 2, 0]);
|
||||||
|
expect(changeList, []);
|
||||||
expect(insertList, [0, 1, 2, 0, 0, 0, 1, 2]);
|
|
||||||
expect(changeList, [0, 0, 0, 1, 2]);
|
|
||||||
expect(removeList, [0]);
|
expect(removeList, [0]);
|
||||||
expect(timeline.events.length, 7);
|
expect(timeline.events.length, 3);
|
||||||
expect(timeline.events[0].status, EventStatus.error);
|
expect(timeline.events[0].status, EventStatus.synced);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getEventById', () async {
|
test('getEventById', () async {
|
||||||
|
await timeline.requestFuture();
|
||||||
|
await timeline.requestFuture();
|
||||||
|
client.onEvent.add(EventUpdate(
|
||||||
|
type: EventUpdateType.timeline,
|
||||||
|
roomID: roomID,
|
||||||
|
content: {
|
||||||
|
'type': 'm.room.message',
|
||||||
|
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||||
|
'sender': '@alice:example.com',
|
||||||
|
'status': EventStatus.sending.intValue,
|
||||||
|
'event_id': 'abc',
|
||||||
|
'origin_server_ts': testTimeStamp
|
||||||
|
},
|
||||||
|
));
|
||||||
|
await waitForCount(7);
|
||||||
var event = await timeline.getEventById('abc');
|
var event = await timeline.getEventById('abc');
|
||||||
expect(event?.content, {'msgtype': 'm.text', 'body': 'Testcase'});
|
expect(event?.content, {'msgtype': 'm.text', 'body': 'Testcase'});
|
||||||
|
|
||||||
|
|
@ -240,7 +326,8 @@ void main() {
|
||||||
|
|
||||||
test('Resend message', () async {
|
test('Resend message', () async {
|
||||||
timeline.events.clear();
|
timeline.events.clear();
|
||||||
updateCount = 0;
|
await timeline.requestFuture();
|
||||||
|
await timeline.requestFuture();
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
|
|
@ -254,32 +341,46 @@ void main() {
|
||||||
'unsigned': {'transaction_id': 'newresend'},
|
'unsigned': {'transaction_id': 'newresend'},
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(7);
|
||||||
expect(timeline.events[0].status, EventStatus.error);
|
expect(timeline.events[0].status, EventStatus.error);
|
||||||
|
|
||||||
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
|
|
||||||
await timeline.events[0].sendAgain();
|
await timeline.events[0].sendAgain();
|
||||||
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await FakeMatrixApi.firstWhere((a) => a.startsWith(
|
||||||
|
'/client/v3/rooms/!1234%3Aexample.com/send/m.room.message/newresend'));
|
||||||
|
|
||||||
expect(updateCount, 3);
|
expect(updateCount, 9);
|
||||||
|
|
||||||
expect(insertList, [0, 1, 2, 0, 0, 0, 1, 2, 0]);
|
expect(insertList, [0, 1, 2, 0]);
|
||||||
expect(changeList, [0, 0, 0, 1, 2, 0, 0]);
|
expect(changeList, [0, 0]);
|
||||||
expect(removeList, [0]);
|
expect(removeList, []);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 4);
|
||||||
expect(timeline.events[0].status, EventStatus.sent);
|
expect(timeline.events[0].status, EventStatus.sent);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Clear cache on limited timeline', () async {
|
test('Clear cache on limited timeline', () async {
|
||||||
client.onSync.add(
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
|
await timeline.requestFuture();
|
||||||
|
await timeline.requestFuture();
|
||||||
|
await client.handleSync(
|
||||||
SyncUpdate(
|
SyncUpdate(
|
||||||
nextBatch: '1234',
|
nextBatch: '1234',
|
||||||
rooms: RoomsUpdate(
|
rooms: RoomsUpdate(
|
||||||
join: {
|
join: {
|
||||||
roomID: JoinedRoomUpdate(
|
roomID: JoinedRoomUpdate(
|
||||||
timeline: TimelineUpdate(
|
timeline:
|
||||||
limited: true,
|
TimelineUpdate(limited: true, prevBatch: 'blah', events: [
|
||||||
prevBatch: 'blah',
|
MatrixEvent(
|
||||||
),
|
eventId: '\$somerandomfox',
|
||||||
|
type: 'm.room.message',
|
||||||
|
content: {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||||
|
senderId: '@alice:example.com',
|
||||||
|
originServerTs:
|
||||||
|
DateTime.fromMillisecondsSinceEpoch(testTimeStamp),
|
||||||
|
),
|
||||||
|
]),
|
||||||
unreadNotifications: UnreadNotificationCounts(
|
unreadNotifications: UnreadNotificationCounts(
|
||||||
highlightCount: 0,
|
highlightCount: 0,
|
||||||
notificationCount: 0,
|
notificationCount: 0,
|
||||||
|
|
@ -289,12 +390,14 @@ void main() {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(7);
|
||||||
expect(timeline.events.isEmpty, true);
|
expect(timeline.events.length, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('sort errors on top', () async {
|
test('sort errors on top', () async {
|
||||||
timeline.events.clear();
|
timeline.events.clear();
|
||||||
|
await timeline.requestFuture();
|
||||||
|
await timeline.requestFuture();
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
|
|
@ -319,13 +422,15 @@ void main() {
|
||||||
'origin_server_ts': testTimeStamp + 5
|
'origin_server_ts': testTimeStamp + 5
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(8);
|
||||||
expect(timeline.events[0].status, EventStatus.error);
|
expect(timeline.events[0].status, EventStatus.error);
|
||||||
expect(timeline.events[1].status, EventStatus.synced);
|
expect(timeline.events[1].status, EventStatus.synced);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('sending event to failed update', () async {
|
test('sending event to failed update', () async {
|
||||||
timeline.events.clear();
|
timeline.events.clear();
|
||||||
|
await timeline.requestFuture();
|
||||||
|
await timeline.requestFuture();
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
|
|
@ -338,9 +443,9 @@ void main() {
|
||||||
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(7);
|
||||||
expect(timeline.events[0].status, EventStatus.sending);
|
expect(timeline.events[0].status, EventStatus.sending);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 4);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
|
|
@ -353,11 +458,13 @@ void main() {
|
||||||
'origin_server_ts': testTimeStamp
|
'origin_server_ts': testTimeStamp
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(8);
|
||||||
expect(timeline.events[0].status, EventStatus.error);
|
expect(timeline.events[0].status, EventStatus.error);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 4);
|
||||||
});
|
});
|
||||||
test('setReadMarker', () async {
|
test('setReadMarker', () async {
|
||||||
|
await timeline.requestFuture();
|
||||||
|
await timeline.requestFuture();
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
|
|
@ -370,7 +477,8 @@ void main() {
|
||||||
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(7);
|
||||||
|
|
||||||
room.notificationCount = 1;
|
room.notificationCount = 1;
|
||||||
await timeline.setReadMarker();
|
await timeline.setReadMarker();
|
||||||
expect(room.notificationCount, 0);
|
expect(room.notificationCount, 0);
|
||||||
|
|
@ -378,6 +486,9 @@ void main() {
|
||||||
test('sending an event and the http request finishes first, 0 -> 1 -> 2',
|
test('sending an event and the http request finishes first, 0 -> 1 -> 2',
|
||||||
() async {
|
() async {
|
||||||
timeline.events.clear();
|
timeline.events.clear();
|
||||||
|
await timeline.requestFuture();
|
||||||
|
await timeline.requestFuture();
|
||||||
|
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
|
|
@ -390,9 +501,9 @@ void main() {
|
||||||
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(7);
|
||||||
expect(timeline.events[0].status, EventStatus.sending);
|
expect(timeline.events[0].status, EventStatus.sending);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 4);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
|
|
@ -406,9 +517,9 @@ void main() {
|
||||||
'unsigned': {'transaction_id': 'transaction'}
|
'unsigned': {'transaction_id': 'transaction'}
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(8);
|
||||||
expect(timeline.events[0].status, EventStatus.sent);
|
expect(timeline.events[0].status, EventStatus.sent);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 4);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
|
|
@ -422,13 +533,16 @@ void main() {
|
||||||
'unsigned': {'transaction_id': 'transaction'}
|
'unsigned': {'transaction_id': 'transaction'}
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(9);
|
||||||
expect(timeline.events[0].status, EventStatus.synced);
|
expect(timeline.events[0].status, EventStatus.synced);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 4);
|
||||||
});
|
});
|
||||||
test('sending an event where the sync reply arrives first, 0 -> 2 -> 1',
|
test('sending an event where the sync reply arrives first, 0 -> 2 -> 1',
|
||||||
() async {
|
() async {
|
||||||
timeline.events.clear();
|
timeline.events.clear();
|
||||||
|
await timeline.requestFuture();
|
||||||
|
await timeline.requestFuture();
|
||||||
|
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
|
|
@ -444,9 +558,9 @@ void main() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(7);
|
||||||
expect(timeline.events[0].status, EventStatus.sending);
|
expect(timeline.events[0].status, EventStatus.sending);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 4);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
|
|
@ -462,9 +576,9 @@ void main() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(8);
|
||||||
expect(timeline.events[0].status, EventStatus.synced);
|
expect(timeline.events[0].status, EventStatus.synced);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 4);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
|
|
@ -480,12 +594,15 @@ void main() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(9);
|
||||||
expect(timeline.events[0].status, EventStatus.synced);
|
expect(timeline.events[0].status, EventStatus.synced);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 4);
|
||||||
});
|
});
|
||||||
test('sending an event 0 -> -1 -> 2', () async {
|
test('sending an event 0 -> -1 -> 2', () async {
|
||||||
timeline.events.clear();
|
timeline.events.clear();
|
||||||
|
await timeline.requestFuture();
|
||||||
|
await timeline.requestFuture();
|
||||||
|
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
|
|
@ -498,9 +615,9 @@ void main() {
|
||||||
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(7);
|
||||||
expect(timeline.events[0].status, EventStatus.sending);
|
expect(timeline.events[0].status, EventStatus.sending);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 4);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
|
|
@ -513,9 +630,9 @@ void main() {
|
||||||
'unsigned': {'transaction_id': 'transaction'},
|
'unsigned': {'transaction_id': 'transaction'},
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(8);
|
||||||
expect(timeline.events[0].status, EventStatus.error);
|
expect(timeline.events[0].status, EventStatus.error);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 4);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
|
|
@ -529,12 +646,15 @@ void main() {
|
||||||
'unsigned': {'transaction_id': 'transaction'},
|
'unsigned': {'transaction_id': 'transaction'},
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(9);
|
||||||
expect(timeline.events[0].status, EventStatus.synced);
|
expect(timeline.events[0].status, EventStatus.synced);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 4);
|
||||||
});
|
});
|
||||||
test('sending an event 0 -> 2 -> -1', () async {
|
test('sending an event 0 -> 2 -> -1', () async {
|
||||||
timeline.events.clear();
|
timeline.events.clear();
|
||||||
|
await timeline.requestFuture();
|
||||||
|
await timeline.requestFuture();
|
||||||
|
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
|
|
@ -547,9 +667,9 @@ void main() {
|
||||||
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(7);
|
||||||
expect(timeline.events[0].status, EventStatus.sending);
|
expect(timeline.events[0].status, EventStatus.sending);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 4);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
|
|
@ -563,9 +683,9 @@ void main() {
|
||||||
'unsigned': {'transaction_id': 'transaction'},
|
'unsigned': {'transaction_id': 'transaction'},
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(8);
|
||||||
expect(timeline.events[0].status, EventStatus.synced);
|
expect(timeline.events[0].status, EventStatus.synced);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 4);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
|
|
@ -578,9 +698,9 @@ void main() {
|
||||||
'unsigned': {'transaction_id': 'transaction'},
|
'unsigned': {'transaction_id': 'transaction'},
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(9);
|
||||||
expect(timeline.events[0].status, EventStatus.synced);
|
expect(timeline.events[0].status, EventStatus.synced);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 4);
|
||||||
});
|
});
|
||||||
test('logout', () async {
|
test('logout', () async {
|
||||||
await client.logout();
|
await client.logout();
|
||||||
|
|
|
||||||
|
|
@ -16,28 +16,57 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
import 'package:matrix/src/models/timeline_chunk.dart';
|
import 'package:matrix/src/models/timeline_chunk.dart';
|
||||||
|
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'package:olm/olm.dart' as olm;
|
import 'package:olm/olm.dart' as olm;
|
||||||
import 'fake_client.dart';
|
import 'fake_client.dart';
|
||||||
|
import 'fake_matrix_api.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('Timeline', () {
|
group('Timeline', () {
|
||||||
Logs().level = Level.error;
|
Logs().level = Level.error;
|
||||||
final roomID = '!1234:example.com';
|
final roomID = '!1234:example.com';
|
||||||
final testTimeStamp = DateTime.now().millisecondsSinceEpoch;
|
var testTimeStamp = 0;
|
||||||
var updateCount = 0;
|
var updateCount = 0;
|
||||||
final insertList = <int>[];
|
final insertList = <int>[];
|
||||||
final changeList = <int>[];
|
final changeList = <int>[];
|
||||||
final removeList = <int>[];
|
final removeList = <int>[];
|
||||||
var olmEnabled = true;
|
var olmEnabled = true;
|
||||||
|
var currentPoison = 0;
|
||||||
|
|
||||||
|
final countStream = StreamController<int>.broadcast();
|
||||||
|
Future<int> waitForCount(int count) {
|
||||||
|
if (updateCount == count) {
|
||||||
|
return Future.value(updateCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
final completer = Completer<int>();
|
||||||
|
|
||||||
|
StreamSubscription<int>? sub;
|
||||||
|
sub = countStream.stream.listen((newCount) {
|
||||||
|
if (newCount == count) {
|
||||||
|
sub?.cancel();
|
||||||
|
completer.complete(count);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return completer.future.timeout(Duration(seconds: 1),
|
||||||
|
onTimeout: () async {
|
||||||
|
throw TimeoutException(
|
||||||
|
'Failed to wait for updateCount == $count, current == $updateCount',
|
||||||
|
Duration(seconds: 1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
late Client client;
|
late Client client;
|
||||||
late Room room;
|
late Room room;
|
||||||
late Timeline timeline;
|
late Timeline timeline;
|
||||||
test('create stuff', () async {
|
setUp(() async {
|
||||||
try {
|
try {
|
||||||
await olm.init();
|
await olm.init();
|
||||||
olm.get_library_version();
|
olm.get_library_version();
|
||||||
|
|
@ -49,24 +78,40 @@ void main() {
|
||||||
client = await getClient();
|
client = await getClient();
|
||||||
client.sendMessageTimeoutSeconds = 5;
|
client.sendMessageTimeoutSeconds = 5;
|
||||||
|
|
||||||
|
final poison = Random().nextInt(2 ^ 32);
|
||||||
|
currentPoison = poison;
|
||||||
|
|
||||||
room = Room(
|
room = Room(
|
||||||
id: roomID, client: client, prev_batch: '1234', roomAccountData: {});
|
id: roomID, client: client, prev_batch: '1234', roomAccountData: {});
|
||||||
timeline = Timeline(
|
timeline = Timeline(
|
||||||
room: room,
|
room: room,
|
||||||
chunk: TimelineChunk(events: []),
|
chunk: TimelineChunk(events: []),
|
||||||
onUpdate: () {
|
onUpdate: () {
|
||||||
|
if (poison != currentPoison) return;
|
||||||
updateCount++;
|
updateCount++;
|
||||||
|
countStream.add(updateCount);
|
||||||
},
|
},
|
||||||
onInsert: insertList.add,
|
onInsert: insertList.add,
|
||||||
onChange: changeList.add,
|
onChange: changeList.add,
|
||||||
onRemove: removeList.add,
|
onRemove: removeList.add,
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
test('Create', () async {
|
|
||||||
await client.checkHomeserver(Uri.parse('https://fakeserver.notexisting'),
|
await client.checkHomeserver(Uri.parse('https://fakeserver.notexisting'),
|
||||||
checkWellKnown: false);
|
checkWellKnown: false);
|
||||||
|
await client.abortSync();
|
||||||
|
|
||||||
|
updateCount = 0;
|
||||||
|
insertList.clear();
|
||||||
|
changeList.clear();
|
||||||
|
removeList.clear();
|
||||||
|
|
||||||
|
await client.abortSync();
|
||||||
|
testTimeStamp = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() => client.dispose(closeDatabase: true).onError((e, s) {}));
|
||||||
|
|
||||||
|
test('Create', () async {
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
|
|
@ -94,7 +139,7 @@ void main() {
|
||||||
|
|
||||||
expect(timeline.sub != null, true);
|
expect(timeline.sub != null, true);
|
||||||
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(2);
|
||||||
|
|
||||||
expect(updateCount, 2);
|
expect(updateCount, 2);
|
||||||
expect(insertList, [0, 0]);
|
expect(insertList, [0, 0]);
|
||||||
|
|
@ -143,7 +188,7 @@ void main() {
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(3);
|
||||||
|
|
||||||
expect(updateCount, 3);
|
expect(updateCount, 3);
|
||||||
expect(insertList, [0, 0, 0]);
|
expect(insertList, [0, 0, 0]);
|
||||||
|
|
@ -157,10 +202,9 @@ void main() {
|
||||||
test('Send message', () async {
|
test('Send message', () async {
|
||||||
await room.sendTextEvent('test', txid: '1234');
|
await room.sendTextEvent('test', txid: '1234');
|
||||||
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(2);
|
||||||
|
expect(updateCount, 2);
|
||||||
expect(updateCount, 5);
|
expect(insertList, [0]);
|
||||||
expect(insertList, [0, 0, 0, 0]);
|
|
||||||
expect(insertList.length, timeline.events.length);
|
expect(insertList.length, timeline.events.length);
|
||||||
final eventId = timeline.events[0].eventId;
|
final eventId = timeline.events[0].eventId;
|
||||||
expect(eventId.startsWith('\$event'), true);
|
expect(eventId.startsWith('\$event'), true);
|
||||||
|
|
@ -180,16 +224,52 @@ void main() {
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(3);
|
||||||
|
expect(updateCount, 3);
|
||||||
expect(updateCount, 6);
|
expect(insertList, [0]);
|
||||||
expect(insertList, [0, 0, 0, 0]);
|
|
||||||
expect(insertList.length, timeline.events.length);
|
expect(insertList.length, timeline.events.length);
|
||||||
expect(timeline.events[0].eventId, eventId);
|
expect(timeline.events[0].eventId, eventId);
|
||||||
expect(timeline.events[0].status, EventStatus.synced);
|
expect(timeline.events[0].status, EventStatus.synced);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Send message with error', () async {
|
test('Send message with error', () async {
|
||||||
|
client.onEvent.add(EventUpdate(
|
||||||
|
type: EventUpdateType.timeline,
|
||||||
|
roomID: roomID,
|
||||||
|
content: {
|
||||||
|
'type': 'm.room.message',
|
||||||
|
'content': {
|
||||||
|
'msgtype': 'm.text',
|
||||||
|
'body': 'Testcase should not show up in Sync'
|
||||||
|
},
|
||||||
|
'sender': '@alice:example.com',
|
||||||
|
'status': EventStatus.sending.intValue,
|
||||||
|
'event_id': 'abc',
|
||||||
|
'origin_server_ts': testTimeStamp
|
||||||
|
},
|
||||||
|
));
|
||||||
|
await waitForCount(1);
|
||||||
|
|
||||||
|
await room.sendTextEvent('test', txid: 'errortxid');
|
||||||
|
await waitForCount(3);
|
||||||
|
|
||||||
|
await room.sendTextEvent('test', txid: 'errortxid2');
|
||||||
|
await waitForCount(5);
|
||||||
|
await room.sendTextEvent('test', txid: 'errortxid3');
|
||||||
|
await waitForCount(7);
|
||||||
|
|
||||||
|
expect(updateCount, 7);
|
||||||
|
expect(insertList, [0, 0, 1, 2]);
|
||||||
|
expect(insertList.length, timeline.events.length);
|
||||||
|
expect(changeList, [0, 1, 2]);
|
||||||
|
expect(removeList, []);
|
||||||
|
expect(timeline.events[0].status, EventStatus.error);
|
||||||
|
expect(timeline.events[1].status, EventStatus.error);
|
||||||
|
expect(timeline.events[2].status, EventStatus.error);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Remove message', () async {
|
||||||
|
// send a failed message
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
|
|
@ -202,43 +282,32 @@ void main() {
|
||||||
'origin_server_ts': testTimeStamp
|
'origin_server_ts': testTimeStamp
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(1);
|
||||||
|
|
||||||
expect(updateCount, 7);
|
|
||||||
await room.sendTextEvent('test', txid: 'errortxid');
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
|
||||||
|
|
||||||
expect(updateCount, 9);
|
|
||||||
await room.sendTextEvent('test', txid: 'errortxid2');
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
|
||||||
await room.sendTextEvent('test', txid: 'errortxid3');
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
|
||||||
|
|
||||||
expect(updateCount, 13);
|
|
||||||
expect(insertList, [0, 0, 0, 0, 0, 0, 1, 2]);
|
|
||||||
expect(insertList.length, timeline.events.length);
|
|
||||||
expect(changeList, [2, 0, 0, 0, 1, 2]);
|
|
||||||
expect(removeList, []);
|
|
||||||
expect(timeline.events[0].status, EventStatus.error);
|
|
||||||
expect(timeline.events[1].status, EventStatus.error);
|
|
||||||
expect(timeline.events[2].status, EventStatus.error);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Remove message', () async {
|
|
||||||
await timeline.events[0].remove();
|
await timeline.events[0].remove();
|
||||||
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(2);
|
||||||
|
|
||||||
expect(updateCount, 14);
|
expect(insertList, [0]);
|
||||||
|
expect(changeList, []);
|
||||||
expect(insertList, [0, 0, 0, 0, 0, 0, 1, 2]);
|
|
||||||
expect(changeList, [2, 0, 0, 0, 1, 2]);
|
|
||||||
expect(removeList, [0]);
|
expect(removeList, [0]);
|
||||||
expect(timeline.events.length, 7);
|
expect(timeline.events.length, 0);
|
||||||
expect(timeline.events[0].status, EventStatus.error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getEventById', () async {
|
test('getEventById', () async {
|
||||||
|
client.onEvent.add(EventUpdate(
|
||||||
|
type: EventUpdateType.timeline,
|
||||||
|
roomID: roomID,
|
||||||
|
content: {
|
||||||
|
'type': 'm.room.message',
|
||||||
|
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||||
|
'sender': '@alice:example.com',
|
||||||
|
'status': EventStatus.sending.intValue,
|
||||||
|
'event_id': 'abc',
|
||||||
|
'origin_server_ts': testTimeStamp
|
||||||
|
},
|
||||||
|
));
|
||||||
|
await waitForCount(1);
|
||||||
var event = await timeline.getEventById('abc');
|
var event = await timeline.getEventById('abc');
|
||||||
expect(event?.content, {'msgtype': 'm.text', 'body': 'Testcase'});
|
expect(event?.content, {'msgtype': 'm.text', 'body': 'Testcase'});
|
||||||
|
|
||||||
|
|
@ -270,17 +339,17 @@ void main() {
|
||||||
'unsigned': {'transaction_id': 'newresend'},
|
'unsigned': {'transaction_id': 'newresend'},
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(1);
|
||||||
expect(timeline.events[0].status, EventStatus.error);
|
expect(timeline.events[0].status, EventStatus.error);
|
||||||
await timeline.events[0].sendAgain();
|
await timeline.events[0].sendAgain();
|
||||||
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(3);
|
||||||
|
|
||||||
expect(updateCount, 17);
|
expect(updateCount, 3);
|
||||||
|
|
||||||
expect(insertList, [0, 0, 0, 0, 0, 0, 1, 2, 0]);
|
expect(insertList, [0]);
|
||||||
expect(changeList, [2, 0, 0, 0, 1, 2, 0, 0]);
|
expect(changeList, [0, 0]);
|
||||||
expect(removeList, [0]);
|
expect(removeList, []);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
expect(timeline.events[0].status, EventStatus.sent);
|
expect(timeline.events[0].status, EventStatus.sent);
|
||||||
});
|
});
|
||||||
|
|
@ -290,10 +359,10 @@ void main() {
|
||||||
expect(timeline.canRequestHistory, true);
|
expect(timeline.canRequestHistory, true);
|
||||||
await room.requestHistory();
|
await room.requestHistory();
|
||||||
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await waitForCount(3);
|
||||||
|
|
||||||
expect(updateCount, 20);
|
expect(updateCount, 3);
|
||||||
expect(insertList, [0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 1, 2]);
|
expect(insertList, [0, 1, 2]);
|
||||||
expect(timeline.events.length, 3);
|
expect(timeline.events.length, 3);
|
||||||
expect(timeline.events[0].eventId, '3143273582443PhrSn:example.org');
|
expect(timeline.events[0].eventId, '3143273582443PhrSn:example.org');
|
||||||
expect(timeline.events[1].eventId, '2143273582443PhrSn:example.org');
|
expect(timeline.events[1].eventId, '2143273582443PhrSn:example.org');
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,7 @@ void main() {
|
||||||
expect(user2.mentionFragments, {'@Bob', '@Bob#1542'});
|
expect(user2.mentionFragments, {'@Bob', '@Bob#1542'});
|
||||||
});
|
});
|
||||||
test('dispose client', () async {
|
test('dispose client', () async {
|
||||||
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
await client.dispose(closeDatabase: true);
|
await client.dispose(closeDatabase: true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue