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