Merge branch 'master' into soru/cross-signing

This commit is contained in:
Sorunome 2020-05-25 13:22:13 +02:00
commit 97a10c7de1
No known key found for this signature in database
GPG Key ID: B19471D07FC9BE9C
20 changed files with 627 additions and 78 deletions

View File

@ -643,6 +643,10 @@ class Client {
final StreamController<MatrixException> onError =
StreamController.broadcast();
/// Synchronization erros are coming here.
final StreamController<ToDeviceEventDecryptionError> onOlmError =
StreamController.broadcast();
/// This is called once, when the first sync has received.
final StreamController<bool> onFirstSync = StreamController.broadcast();
@ -1138,11 +1142,18 @@ class Client {
if (toDeviceEvent.type == 'm.room.encrypted') {
try {
toDeviceEvent = decryptToDeviceEvent(toDeviceEvent);
} catch (e) {
} catch (e, s) {
print(
'[LibOlm] Could not decrypt to device event from ${toDeviceEvent.sender}: ' +
e.toString());
print(toDeviceEvent.sender);
'[LibOlm] Could not decrypt to device event from ${toDeviceEvent.sender} with content: ${toDeviceEvent.content}');
print(e);
print(s);
onOlmError.add(
ToDeviceEventDecryptionError(
exception: e,
stackTrace: s,
toDeviceEvent: toDeviceEvent,
),
);
toDeviceEvent = ToDeviceEvent.fromJson(events[i]);
}
}
@ -1570,7 +1581,7 @@ class Client {
deviceKeys.verified &&
!deviceKeys.blocked) {
await roomKeyRequest.forwardKey();
} else {
} else if (roomKeyRequest.requestingDevice != null) {
onRoomKeyRequest.add(roomKeyRequest);
}
}

View File

@ -425,6 +425,25 @@ class Event {
/// if it fails and does nothing if the event was not encrypted.
Event get decrypted => room.decryptGroupMessage(this);
/// Trys to decrypt this event and persists it in the database afterwards
Future<Event> decryptAndStore([String updateType = 'timeline']) async {
final newEvent = decrypted;
if (newEvent.type == EventTypes.Encrypted || room.client.database == null) {
return newEvent; // decryption failed or we don't have a database
}
await room.client.database.storeEventUpdate(
room.client.id,
EventUpdate(
eventType: newEvent.typeKey,
content: newEvent.toJson(),
roomID: newEvent.roomId,
type: updateType,
sortOrder: newEvent.sortOrder,
),
);
return newEvent;
}
/// If this event is encrypted and the decryption was not successful because
/// the session is unknown, this requests the session key from other devices
/// in the room. If the event is not encrypted or the decryption failed because

View File

@ -271,9 +271,9 @@ class Room {
onSessionKeyReceived.add(sessionId);
}
void _tryAgainDecryptLastMessage() {
Future<void> _tryAgainDecryptLastMessage() async {
if (getState('m.room.encrypted') != null) {
final decrypted = getState('m.room.encrypted').decrypted;
final decrypted = await getState('m.room.encrypted').decryptAndStore();
if (decrypted.type != EventTypes.Encrypted) {
setState(decrypted);
}
@ -1207,19 +1207,7 @@ class Room {
if (events[i].type == EventTypes.Encrypted &&
events[i].content['body'] == DecryptError.UNKNOWN_SESSION) {
await events[i].loadSession();
events[i] = events[i].decrypted;
if (events[i].type != EventTypes.Encrypted) {
await client.database.storeEventUpdate(
client.id,
EventUpdate(
eventType: events[i].typeKey,
content: events[i].toJson(),
roomID: events[i].roomId,
type: 'timeline',
sortOrder: events[i].sortOrder,
),
);
}
events[i] = await events[i].decryptAndStore();
}
}
});

View File

@ -97,18 +97,25 @@ class Timeline {
sessionIdReceivedSub?.cancel();
}
void _sessionKeyReceived(String sessionId) {
void _sessionKeyReceived(String sessionId) async {
var decryptAtLeastOneEvent = false;
for (var i = 0; i < events.length; i++) {
if (events[i].type == EventTypes.Encrypted &&
events[i].messageType == MessageTypes.BadEncrypted &&
events[i].content['body'] == DecryptError.UNKNOWN_SESSION &&
events[i].content['session_id'] == sessionId) {
events[i] = events[i].decrypted;
if (events[i].type != EventTypes.Encrypted) {
decryptAtLeastOneEvent = true;
final decryptFn = () async {
for (var i = 0; i < events.length; i++) {
if (events[i].type == EventTypes.Encrypted &&
events[i].messageType == MessageTypes.BadEncrypted &&
events[i].content['body'] == DecryptError.UNKNOWN_SESSION &&
events[i].content['session_id'] == sessionId) {
events[i] = await events[i].decryptAndStore();
if (events[i].type != EventTypes.Encrypted) {
decryptAtLeastOneEvent = true;
}
}
}
};
if (room.client.database != null) {
await room.client.database.transaction(decryptFn);
} else {
await decryptFn();
}
if (decryptAtLeastOneEvent) onUpdate();
}

View File

@ -1,6 +1,7 @@
import '../room.dart';
abstract class MatrixLocalizations {
const MatrixLocalizations();
String get emptyChat;
String get invitedUsersOnly;
@ -121,9 +122,8 @@ extension HistoryVisibilityDisplayString on HistoryVisibility {
return i18n.visibleForAllParticipants;
case HistoryVisibility.world_readable:
return i18n.visibleForEveryone;
default:
return toString().replaceAll('HistoryVisibility.', '');
}
return null;
}
}
@ -134,9 +134,8 @@ extension GuestAccessDisplayString on GuestAccess {
return i18n.guestsCanJoin;
case GuestAccess.forbidden:
return i18n.guestsAreForbidden;
default:
return toString().replaceAll('GuestAccess.', '');
}
return null;
}
}

View File

@ -4,12 +4,6 @@ class OpenIdCredentials {
String matrixServerName;
num expiresIn;
OpenIdCredentials(
{this.accessToken,
this.tokenType,
this.matrixServerName,
this.expiresIn});
OpenIdCredentials.fromJson(Map<String, dynamic> json) {
accessToken = json['access_token'];
tokenType = json['token_type'];

View File

@ -7,14 +7,6 @@ class PublicRoomsResponse {
final int totalRoomCountEstimate;
Client client;
PublicRoomsResponse({
this.publicRooms,
this.nextBatch,
this.prevBatch,
this.totalRoomCountEstimate,
this.client,
});
PublicRoomsResponse.fromJson(Map<String, dynamic> json, Client client)
: nextBatch = json['next_batch'],
prevBatch = json['prev_batch'],
@ -42,18 +34,6 @@ class PublicRoomEntry {
Future<void> join() => client.joinRoomById(roomId);
PublicRoomEntry({
this.aliases,
this.avatarUrl,
this.guestCanJoin,
this.name,
this.numJoinedMembers,
this.roomId,
this.topic,
this.worldReadable,
this.client,
});
PublicRoomEntry.fromJson(Map<String, dynamic> json, Client client)
: aliases =
json.containsKey('aliases') ? json['aliases'].cast<String>() : [],

View File

@ -8,16 +8,6 @@ class Pusher {
String lang;
PusherData data;
Pusher(
{this.pushkey,
this.kind,
this.appId,
this.appDisplayName,
this.deviceDisplayName,
this.profileTag,
this.lang,
this.data});
Pusher.fromJson(Map<String, dynamic> json) {
pushkey = json['pushkey'];
kind = json['kind'];
@ -49,8 +39,6 @@ class PusherData {
String url;
String format;
PusherData({this.url, this.format});
PusherData.fromJson(Map<String, dynamic> json) {
url = json['url'];
format = json['format'];
@ -58,8 +46,8 @@ class PusherData {
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['url'] = url;
data['format'] = format;
if (url != null) data['url'] = url;
if (format != null) data['format'] = format;
return data;
}
}

View File

@ -22,7 +22,7 @@ class RoomKeyRequest extends ToDeviceEvent {
for (final key in session.forwardingCurve25519KeyChain) {
forwardedKeys.add(key);
}
await requestingDevice?.setVerified(true);
await requestingDevice.setVerified(true);
var message = session.content;
message['forwarding_curve25519_key_chain'] = forwardedKeys;

View File

@ -24,3 +24,17 @@ class ToDeviceEvent {
return data;
}
}
class ToDeviceEventDecryptionError extends ToDeviceEvent {
Exception exception;
StackTrace stackTrace;
ToDeviceEventDecryptionError({
ToDeviceEvent toDeviceEvent,
this.exception,
this.stackTrace,
}) : super(
sender: toDeviceEvent.sender,
content: toDeviceEvent.content,
type: toDeviceEvent.type,
);
}

View File

@ -3,8 +3,6 @@ class WellKnownInformations {
MHomeserver mIdentityServer;
Map<String, dynamic> content;
WellKnownInformations({this.mHomeserver, this.mIdentityServer});
WellKnownInformations.fromJson(Map<String, dynamic> json) {
content = json;
mHomeserver = json['m.homeserver'] != null
@ -19,8 +17,6 @@ class WellKnownInformations {
class MHomeserver {
String baseUrl;
MHomeserver({this.baseUrl});
MHomeserver.fromJson(Map<String, dynamic> json) {
baseUrl = json['base_url'];
}

View File

@ -411,6 +411,12 @@ void main() {
expect(openId.tokenType, 'Bearer');
expect(openId.matrixServerName, 'example.com');
expect(openId.expiresIn, 3600);
expect(openId.toJson(), {
'access_token': 'SomeT0kenHere',
'token_type': 'Bearer',
'matrix_server_name': 'example.com',
'expires_in': 3600
});
});
test('createRoom', () async {

View File

@ -0,0 +1,213 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
*
* This file is part of famedlysdk.
*
* famedlysdk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* famedlysdk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:famedlysdk/famedlysdk.dart';
class MatrixDefaultLocalizations extends MatrixLocalizations {
const MatrixDefaultLocalizations();
@override
String acceptedTheInvitation(String targetName) =>
'$targetName accepted the invitation';
@override
String activatedEndToEndEncryption(String senderName) =>
'$senderName activated end to end encryption';
@override
String get anyoneCanJoin => 'Anyone can join';
@override
String bannedUser(String senderName, String targetName) =>
'$senderName banned $targetName';
@override
String changedTheChatAvatar(String senderName) =>
'$senderName changed the chat avatar';
@override
String changedTheChatDescriptionTo(String senderName, String content) =>
'$senderName changed the chat description to $content';
@override
String changedTheChatNameTo(String senderName, String content) =>
'$senderName changed the chat name to $content';
@override
String changedTheChatPermissions(String senderName) =>
'$senderName changed the chat permissions';
@override
String changedTheDisplaynameTo(String targetName, String newDisplayname) =>
'$targetName changed the displayname to $newDisplayname';
@override
String changedTheGuestAccessRules(String senderName) =>
'$senderName changed the guest access rules';
@override
String changedTheGuestAccessRulesTo(
String senderName, String localizedString) =>
'$senderName changed the guest access rules to $localizedString';
@override
String changedTheHistoryVisibility(String senderName) =>
'$senderName changed the history visibility';
@override
String changedTheHistoryVisibilityTo(
String senderName, String localizedString) =>
'$senderName changed the history visibility to $localizedString';
@override
String changedTheJoinRules(String senderName) =>
'$senderName changed the join rules';
@override
String changedTheJoinRulesTo(String senderName, String localizedString) =>
'$senderName changed the join rules to $localizedString';
@override
String changedTheProfileAvatar(String targetName) =>
'$targetName changed the profile avatar';
@override
String changedTheRoomAliases(String senderName) =>
'$senderName changed the room aliases';
@override
String changedTheRoomInvitationLink(String senderName) =>
'$senderName changed the room invitation link';
@override
String get channelCorruptedDecryptError =>
'The secure channel has been corrupted';
@override
String couldNotDecryptMessage(String errorText) =>
'Could not decrypt message: $errorText';
@override
String createdTheChat(String senderName) => '$senderName created the chat';
@override
String get emptyChat => 'Empty chat';
@override
String get encryptionNotEnabled => 'Encryption not enabled';
@override
String get fromJoining => 'From joining';
@override
String get fromTheInvitation => 'From the invitation';
@override
String groupWith(String displayname) => 'Group with $displayname';
@override
String get guestsAreForbidden => 'Guests are forbidden';
@override
String get guestsCanJoin => 'Guests can join';
@override
String hasWithdrawnTheInvitationFor(String senderName, String targetName) =>
'$senderName has withdrawn the invitation for $targetName';
@override
String invitedUser(String senderName, String targetName) =>
'$senderName has invited $targetName';
@override
String get invitedUsersOnly => 'Invited users only';
@override
String joinedTheChat(String targetName) => '$targetName joined the chat';
@override
String kicked(String senderName, String targetName) =>
'$senderName kicked $targetName';
@override
String kickedAndBanned(String senderName, String targetName) =>
'$senderName banned $targetName';
@override
String get needPantalaimonWarning => 'Need pantalaimon';
@override
String get noPermission => 'No permission';
@override
String redactedAnEvent(String senderName) => '$senderName redacted an event';
@override
String rejectedTheInvitation(String targetName) =>
'$targetName rejected the invitation';
@override
String removedBy(String calcDisplayname) => 'Removed by $calcDisplayname';
@override
String get roomHasBeenUpgraded => 'Room has been upgraded';
@override
String sentAFile(String senderName) => '$senderName sent a file';
@override
String sentAPicture(String senderName) => '$senderName sent a picture';
@override
String sentASticker(String senderName) => '$senderName sent a sticker';
@override
String sentAVideo(String senderName) => '$senderName sent a video';
@override
String sentAnAudio(String senderName) => '$senderName sent an audio';
@override
String sharedTheLocation(String senderName) =>
'$senderName shared the location';
@override
String unbannedUser(String senderName, String targetName) =>
'$senderName unbanned $targetName';
@override
String get unknownEncryptionAlgorithm => 'Unknown encryption algorithm';
@override
String unknownEvent(String typeKey) => 'Unknown event $typeKey';
@override
String userLeftTheChat(String targetName) => '$targetName left the chat';
@override
String get visibleForAllParticipants => 'Visible for all participants';
@override
String get visibleForEveryone => 'Visible for everyone';
@override
String get you => 'You';
}

View File

@ -0,0 +1,71 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
*
* This file is part of famedlysdk.
*
* famedlysdk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* famedlysdk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:famedlysdk/famedlysdk.dart';
import 'package:http/http.dart';
import 'package:test/test.dart';
void main() {
/// All Tests related to device keys
group('Matrix Exception', () {
test('Matrix Exception', () async {
final matrixException = MatrixException(
Response(
'{"flows":[{"stages":["example.type.foo"]}],"params":{"example.type.baz":{"example_key":"foobar"}},"session":"xxxxxxyz","completed":["example.type.foo"]}',
401,
),
);
expect(matrixException.errcode, 'M_FORBIDDEN');
final flows = matrixException.authenticationFlows;
expect(flows.length, 1);
expect(flows.first.stages.length, 1);
expect(flows.first.stages.first, 'example.type.foo');
expect(
matrixException.authenticationParams['example.type.baz'],
{'example_key': 'foobar'},
);
expect(matrixException.completedAuthenticationFlows.length, 1);
expect(matrixException.completedAuthenticationFlows.first,
'example.type.foo');
expect(matrixException.session, 'xxxxxxyz');
});
test('Unknown Exception', () async {
final matrixException = MatrixException(
Response(
'{"errcode":"M_HAHA","error":"HAHA","retry_after_ms":500}',
401,
),
);
expect(matrixException.error, MatrixError.M_UNKNOWN);
expect(matrixException.retryAfterMs, 500);
});
test('Missing Exception', () async {
final matrixException = MatrixException(
Response(
'{"error":"HAHA"}',
401,
),
);
expect(matrixException.error, MatrixError.M_UNKNOWN);
});
});
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
*
* This file is part of famedlysdk.
*
* famedlysdk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* famedlysdk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:typed_data';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:test/test.dart';
import 'package:olm/olm.dart' as olm;
void main() {
/// All Tests related to device keys
group('Matrix File', () {
test('Decrypt', () async {
final text = 'hello world';
final file = MatrixFile(
path: '/path/to/file.txt',
bytes: Uint8List.fromList(text.codeUnits),
);
var olmEnabled = true;
try {
await olm.init();
olm.Account();
} catch (_) {
olmEnabled = false;
}
if (olmEnabled) {
final encryptedFile = await file.encrypt();
expect(encryptedFile != null, true);
}
});
});
}

View File

@ -45,6 +45,8 @@ void main() {
expect('\$test:example.com'.sigil, '\$');
expect(mxId.localpart, 'test');
expect(mxId.domain, 'example.com');
expect(mxId.equals('@Test:example.com'), true);
expect(mxId.equals('@test:example.org'), false);
});
});
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
*
* This file is part of famedlysdk.
*
* famedlysdk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* famedlysdk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:famedlysdk/famedlysdk.dart';
import 'package:test/test.dart';
import 'matrix_default_localizations.dart';
void main() {
/// All Tests related to device keys
group('Matrix Localizations', () {
test('Matrix Localizations', () {
expect(
HistoryVisibility.invited
.getLocalizedString(MatrixDefaultLocalizations()),
'From the invitation');
expect(
HistoryVisibility.joined
.getLocalizedString(MatrixDefaultLocalizations()),
'From joining');
expect(
HistoryVisibility.shared
.getLocalizedString(MatrixDefaultLocalizations()),
'Visible for all participants');
expect(
HistoryVisibility.world_readable
.getLocalizedString(MatrixDefaultLocalizations()),
'Visible for everyone');
expect(
GuestAccess.can_join.getLocalizedString(MatrixDefaultLocalizations()),
'Guests can join');
expect(
GuestAccess.forbidden
.getLocalizedString(MatrixDefaultLocalizations()),
'Guests are forbidden');
expect(JoinRules.invite.getLocalizedString(MatrixDefaultLocalizations()),
'Invited users only');
expect(JoinRules.public.getLocalizedString(MatrixDefaultLocalizations()),
'Anyone can join');
expect(JoinRules.private.getLocalizedString(MatrixDefaultLocalizations()),
'private');
expect(JoinRules.knock.getLocalizedString(MatrixDefaultLocalizations()),
'knock');
});
});
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
*
* This file is part of famedlysdk.
*
* famedlysdk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* famedlysdk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:famedlysdk/famedlysdk.dart';
import 'package:test/test.dart';
import 'fake_matrix_api.dart';
void main() {
/// All Tests related to device keys
group('Public Rooms Response', () {
Client client;
test('Public Rooms Response', () async {
client = Client('testclient', debug: true);
client.httpClient = FakeMatrixApi();
await client.checkServer('https://fakeServer.notExisting');
final responseMap = {
'chunk': [
{
'aliases': ['#murrays:cheese.bar'],
'avatar_url': 'mxc://bleeker.street/CHEDDARandBRIE',
'guest_can_join': false,
'name': 'CHEESE',
'num_joined_members': 37,
'room_id': '1234',
'topic': 'Tasty tasty cheese',
'world_readable': true
}
],
'next_batch': 'p190q',
'prev_batch': 'p1902',
'total_room_count_estimate': 115
};
final publicRoomsResponse =
PublicRoomsResponse.fromJson(responseMap, client);
await publicRoomsResponse.publicRooms.first.join();
});
});
}

45
test/pusher_test.dart Normal file
View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
*
* This file is part of famedlysdk.
*
* famedlysdk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* famedlysdk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:famedlysdk/src/utils/pusher.dart';
import 'package:test/test.dart';
void main() {
/// All Tests related to device keys
group('Pusher', () {
test('Pusher', () {
final rawPusher = {
'pushkey': 'Xp/MzCt8/9DcSNE9cuiaoT5Ac55job3TdLSSmtmYl4A=',
'kind': 'http',
'app_id': 'face.mcapp.appy.prod',
'app_display_name': 'Appy McAppface',
'device_display_name': "Alice's Phone",
'profile_tag': 'xyz',
'lang': 'en-US',
'data': {'url': 'https://example.com/_matrix/push/v1/notify'}
};
final pusher = Pusher.fromJson(rawPusher);
expect(pusher.toJson(), rawPusher);
});
});
}

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
*
* This file is part of famedlysdk.
*
* famedlysdk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* famedlysdk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:famedlysdk/famedlysdk.dart';
import 'package:test/test.dart';
void main() {
/// All Tests related to device keys
group('WellKnownInformations', () {
test('WellKnownInformations', () {
final json = {
'm.homeserver': {'base_url': 'https://matrix.example.com'},
'm.identity_server': {'base_url': 'https://identity.example.com'},
'org.example.custom.property': {
'app_url': 'https://custom.app.example.org'
}
};
WellKnownInformations.fromJson(json);
});
});
}