tests: Add back tests
This commit is contained in:
parent
5dc4286e74
commit
c850924dd3
10
packages/moxxmpp/test/helpers/logging.dart
Normal file
10
packages/moxxmpp/test/helpers/logging.dart
Normal file
@ -0,0 +1,10 @@
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
/// Enable logging using logger.
|
||||
void initLogger() {
|
||||
Logger.root.level = Level.ALL;
|
||||
Logger.root.onRecord.listen((record) {
|
||||
// ignore: avoid_print
|
||||
print('[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}');
|
||||
});
|
||||
}
|
28
packages/moxxmpp/test/helpers/xml.dart
Normal file
28
packages/moxxmpp/test/helpers/xml.dart
Normal file
@ -0,0 +1,28 @@
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
|
||||
bool compareXMLNodes(XMLNode actual, XMLNode expectation, { bool ignoreId = true}) {
|
||||
// Compare attributes
|
||||
if (expectation.tag != actual.tag) return false;
|
||||
|
||||
final attributesEqual = expectation.attributes.keys.every((key) {
|
||||
// Ignore the stanza ID
|
||||
if (key == 'id' && ignoreId) return true;
|
||||
|
||||
return actual.attributes[key] == expectation.attributes[key];
|
||||
});
|
||||
if (!attributesEqual) return false;
|
||||
|
||||
final actualAttributeLength = !ignoreId ? actual.attributes.length : (
|
||||
actual.attributes.containsKey('id') ? actual.attributes.length - 1 : actual.attributes.length
|
||||
);
|
||||
final expectedAttributeLength = !ignoreId ? expectation.attributes.length : (
|
||||
expectation.attributes.containsKey('id') ? expectation.attributes.length - 1 : expectation.attributes.length
|
||||
);
|
||||
if (actualAttributeLength != expectedAttributeLength) return false;
|
||||
|
||||
if (expectation.innerText() != '' && actual.innerText() != expectation.innerText()) return false;
|
||||
|
||||
return expectation.children.every((childe) {
|
||||
return actual.children.any((childa) => compareXMLNodes(childa, childe));
|
||||
});
|
||||
}
|
137
packages/moxxmpp/test/helpers/xmpp.dart
Normal file
137
packages/moxxmpp/test/helpers/xmpp.dart
Normal file
@ -0,0 +1,137 @@
|
||||
import 'dart:async';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'xml.dart';
|
||||
|
||||
T? getNegotiatorNullStub<T extends XmppFeatureNegotiatorBase>(String id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
T? getManagerNullStub<T extends XmppManagerBase>(String id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
abstract class ExpectationBase {
|
||||
|
||||
ExpectationBase(this.expectation, this.response);
|
||||
final String expectation;
|
||||
final String response;
|
||||
|
||||
/// Return true if [input] matches the expectation
|
||||
bool matches(String input);
|
||||
}
|
||||
|
||||
/// Literally compare the input with the expectation
|
||||
class StringExpectation extends ExpectationBase {
|
||||
StringExpectation(String expectation, String response) : super(expectation, response);
|
||||
|
||||
@override
|
||||
bool matches(String input) => input == expectation;
|
||||
}
|
||||
|
||||
///
|
||||
class StanzaExpectation extends ExpectationBase {
|
||||
StanzaExpectation(String expectation, String response, {this.ignoreId = false, this.adjustId = false }) : super(expectation, response);
|
||||
final bool ignoreId;
|
||||
final bool adjustId;
|
||||
|
||||
@override
|
||||
bool matches(String input) {
|
||||
final ex = XMLNode.fromString(expectation);
|
||||
final recv = XMLNode.fromString(expectation);
|
||||
|
||||
return compareXMLNodes(recv, ex, ignoreId: ignoreId);
|
||||
}
|
||||
}
|
||||
|
||||
class StubTCPSocket extends BaseSocketWrapper { // Request -> Response(s)
|
||||
|
||||
StubTCPSocket({ required List<ExpectationBase> play })
|
||||
: _play = play,
|
||||
_dataStream = StreamController<String>.broadcast(),
|
||||
_eventStream = StreamController<XmppSocketEvent>.broadcast();
|
||||
int _state = 0;
|
||||
final StreamController<String> _dataStream;
|
||||
final StreamController<XmppSocketEvent> _eventStream;
|
||||
final List<ExpectationBase> _play;
|
||||
String? lastId;
|
||||
|
||||
@override
|
||||
bool isSecure() => true;
|
||||
|
||||
@override
|
||||
Future<bool> secure(String domain) async => true;
|
||||
|
||||
@override
|
||||
Future<bool> connect(String domain, { String? host, int? port }) async => true;
|
||||
|
||||
@override
|
||||
Stream<String> getDataStream() => _dataStream.stream.asBroadcastStream();
|
||||
@override
|
||||
Stream<XmppSocketEvent> getEventStream() => _eventStream.stream.asBroadcastStream();
|
||||
|
||||
/// Let the "connection" receive [data].
|
||||
void injectRawXml(String data) {
|
||||
print('<== $data');
|
||||
_dataStream.add(data);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(Object? object, { String? redact }) {
|
||||
var str = object as String;
|
||||
// ignore: avoid_print
|
||||
print('==> $str');
|
||||
|
||||
if (_state >= _play.length) {
|
||||
_state++;
|
||||
return;
|
||||
}
|
||||
|
||||
final expectation = _play[_state];
|
||||
|
||||
// TODO: Implement an XML matcher
|
||||
if (str.startsWith("<?xml version='1.0'?>")) {
|
||||
str = str.substring(21);
|
||||
}
|
||||
|
||||
if (str.endsWith('</stream:stream>')) {
|
||||
str = str.substring(0, str.length - 16);
|
||||
}
|
||||
|
||||
if (!expectation.matches(str)) {
|
||||
expect(true, false, reason: 'Expected ${expectation.expectation}, got $str');
|
||||
}
|
||||
|
||||
// Make sure to only progress if everything passed so far
|
||||
_state++;
|
||||
|
||||
var response = expectation.response;
|
||||
if (expectation is StanzaExpectation) {
|
||||
final inputNode = XMLNode.fromString(str);
|
||||
lastId = inputNode.attributes['id'];
|
||||
|
||||
if (expectation.adjustId) {
|
||||
final outputNode = XMLNode.fromString(response);
|
||||
|
||||
outputNode.attributes['id'] = inputNode.attributes['id']!;
|
||||
response = outputNode.toXml();
|
||||
}
|
||||
}
|
||||
|
||||
print("<== $response");
|
||||
_dataStream.add(response);
|
||||
}
|
||||
|
||||
@override
|
||||
void close() {}
|
||||
|
||||
int getState() => _state;
|
||||
void resetState() => _state = 0;
|
||||
|
||||
@override
|
||||
bool whitespacePingAllowed() => true;
|
||||
|
||||
@override
|
||||
bool managesKeepalives() => false;
|
||||
}
|
41
packages/moxxmpp/test/jid_test.dart
Normal file
41
packages/moxxmpp/test/jid_test.dart
Normal file
@ -0,0 +1,41 @@
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('Parse a full JID', () {
|
||||
final jid = JID.fromString('test@server/abc');
|
||||
|
||||
expect(jid.local, 'test');
|
||||
expect(jid.domain, 'server');
|
||||
expect(jid.resource, 'abc');
|
||||
expect(jid.toString(), 'test@server/abc');
|
||||
});
|
||||
|
||||
test('Parse a bare JID', () {
|
||||
final jid = JID.fromString('test@server');
|
||||
|
||||
expect(jid.local, 'test');
|
||||
expect(jid.domain, 'server');
|
||||
expect(jid.resource, '');
|
||||
expect(jid.toString(), 'test@server');
|
||||
});
|
||||
|
||||
test('Parse a JID with no local part', () {
|
||||
final jid = JID.fromString('server/abc');
|
||||
|
||||
expect(jid.local, '');
|
||||
expect(jid.domain, 'server');
|
||||
expect(jid.resource, 'abc');
|
||||
expect(jid.toString(), 'server/abc');
|
||||
});
|
||||
|
||||
test('Equality', () {
|
||||
expect(JID.fromString('hallo@welt/abc') == JID('hallo', 'welt', 'abc'), true);
|
||||
expect(JID.fromString('hallo@welt') == JID('hallo', 'welt', 'a'), false);
|
||||
});
|
||||
|
||||
test('Whitespaces', () {
|
||||
expect(JID.fromString('hallo@welt ') == JID('hallo', 'welt', ''), true);
|
||||
expect(JID.fromString('hallo@welt/abc ') == JID('hallo', 'welt', 'abc'), true);
|
||||
});
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('A group of tests', () {
|
||||
final awesome = Awesome();
|
||||
|
||||
setUp(() {
|
||||
// Additional setup goes here.
|
||||
});
|
||||
|
||||
test('First Test', () {
|
||||
expect(awesome.isAwesome, isTrue);
|
||||
});
|
||||
});
|
||||
}
|
94
packages/moxxmpp/test/negotiator_test.dart
Normal file
94
packages/moxxmpp/test/negotiator_test.dart
Normal file
@ -0,0 +1,94 @@
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'helpers/logging.dart';
|
||||
import 'helpers/xmpp.dart';
|
||||
|
||||
const exampleXmlns1 = 'im:moxxy:example1';
|
||||
const exampleNamespace1 = 'im.moxxy.test.example1';
|
||||
const exampleXmlns2 = 'im:moxxy:example2';
|
||||
const exampleNamespace2 = 'im.moxxy.test.example2';
|
||||
|
||||
class StubNegotiator1 extends XmppFeatureNegotiatorBase {
|
||||
StubNegotiator1() : called = false, super(1, false, exampleXmlns1, exampleNamespace1);
|
||||
|
||||
bool called;
|
||||
|
||||
@override
|
||||
Future<void> negotiate(XMLNode nonza) async {
|
||||
called = true;
|
||||
state = NegotiatorState.done;
|
||||
}
|
||||
}
|
||||
|
||||
class StubNegotiator2 extends XmppFeatureNegotiatorBase {
|
||||
StubNegotiator2() : called = false, super(10, false, exampleXmlns2, exampleNamespace2);
|
||||
|
||||
bool called;
|
||||
|
||||
@override
|
||||
Future<void> negotiate(XMLNode nonza) async {
|
||||
called = true;
|
||||
state = NegotiatorState.done;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
initLogger();
|
||||
|
||||
final stubSocket = StubTCPSocket(
|
||||
play: [
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<example1 xmlns="im:moxxy:example1" />
|
||||
<example2 xmlns="im:moxxy:example2" />
|
||||
</stream:features>''',
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
final connection = XmppConnection(TestingReconnectionPolicy(), stubSocket)
|
||||
..registerFeatureNegotiators([
|
||||
StubNegotiator1(),
|
||||
StubNegotiator2(),
|
||||
])
|
||||
..registerManagers([
|
||||
PresenceManager(),
|
||||
RosterManager(),
|
||||
DiscoManager(),
|
||||
PingManager(),
|
||||
])
|
||||
..setConnectionSettings(
|
||||
ConnectionSettings(
|
||||
jid: JID.fromString('user@test.server'),
|
||||
password: 'abc123',
|
||||
useDirectTLS: true,
|
||||
allowPlainAuth: false,
|
||||
),
|
||||
);
|
||||
final features = [
|
||||
XMLNode.xmlns(tag: 'example1', xmlns: exampleXmlns1),
|
||||
XMLNode.xmlns(tag: 'example2', xmlns: exampleXmlns2),
|
||||
];
|
||||
|
||||
test('Test the priority system', () {
|
||||
expect(connection.getNextNegotiator(features)?.id, exampleNamespace2);
|
||||
});
|
||||
|
||||
test('Test negotiating features with no stream restarts', () async {
|
||||
await connection.connect();
|
||||
await Future.delayed(const Duration(seconds: 3), () {
|
||||
final negotiator1 = connection.getNegotiatorById<StubNegotiator1>(exampleNamespace1);
|
||||
final negotiator2 = connection.getNegotiatorById<StubNegotiator2>(exampleNamespace2);
|
||||
expect(negotiator1?.called, true);
|
||||
expect(negotiator2?.called, true);
|
||||
});
|
||||
});
|
||||
}
|
322
packages/moxxmpp/test/roster_test.dart
Normal file
322
packages/moxxmpp/test/roster_test.dart
Normal file
@ -0,0 +1,322 @@
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
// TODO(PapaTutuWawa): Fix tests
|
||||
|
||||
typedef AddRosterItemFunction = Future<RosterItem> Function(
|
||||
String avatarUrl,
|
||||
String avatarHash,
|
||||
String jid,
|
||||
String title,
|
||||
String subscription,
|
||||
String ask,
|
||||
{
|
||||
List<String> groups,
|
||||
}
|
||||
);
|
||||
|
||||
typedef UpdateRosterItemFunction = Future<RosterItem> Function(
|
||||
int id, {
|
||||
String? avatarUrl,
|
||||
String? avatarHash,
|
||||
String? title,
|
||||
String? subscription,
|
||||
String? ask,
|
||||
List<String>? groups,
|
||||
}
|
||||
);
|
||||
|
||||
AddRosterItemFunction mkAddRosterItem(void Function(String) callback) {
|
||||
return (
|
||||
String avatarUrl,
|
||||
String avatarHash,
|
||||
String jid,
|
||||
String title,
|
||||
String subscription,
|
||||
String ask,
|
||||
{
|
||||
List<String> groups = const [],
|
||||
}
|
||||
) async {
|
||||
callback(jid);
|
||||
return await addRosterItemFromData(
|
||||
avatarUrl,
|
||||
avatarHash,
|
||||
jid,
|
||||
title,
|
||||
subscription,
|
||||
ask,
|
||||
groups: groups,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
Future<RosterItem> addRosterItemFromData(
|
||||
String avatarUrl,
|
||||
String avatarHash,
|
||||
String jid,
|
||||
String title,
|
||||
String subscription,
|
||||
String ask,
|
||||
{
|
||||
List<String> groups = const [],
|
||||
}
|
||||
) async => RosterItem(
|
||||
0,
|
||||
avatarUrl,
|
||||
avatarHash,
|
||||
jid,
|
||||
title,
|
||||
subscription,
|
||||
ask,
|
||||
groups,
|
||||
);
|
||||
|
||||
UpdateRosterItemFunction mkRosterUpdate(List<RosterItem> roster) {
|
||||
return (
|
||||
int id, {
|
||||
String? avatarUrl,
|
||||
String? avatarHash,
|
||||
String? title,
|
||||
String? subscription,
|
||||
String? ask,
|
||||
List<String>? groups,
|
||||
}
|
||||
) async {
|
||||
final item = firstWhereOrNull(roster, (RosterItem item) => item.id == id)!;
|
||||
return item.copyWith(
|
||||
avatarUrl: avatarUrl ?? item.avatarUrl,
|
||||
avatarHash: avatarHash ?? item.avatarHash,
|
||||
title: title ?? item.title,
|
||||
subscription: subscription ?? item.subscription,
|
||||
ask: ask ?? item.ask,
|
||||
groups: groups ?? item.groups,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
void main() {
|
||||
final localRosterSingle = [
|
||||
RosterItem(
|
||||
0,
|
||||
'',
|
||||
'',
|
||||
'hallo@server.example',
|
||||
'hallo',
|
||||
'none',
|
||||
'',
|
||||
[],
|
||||
)
|
||||
];
|
||||
final localRosterDouble = [
|
||||
RosterItem(
|
||||
0,
|
||||
'',
|
||||
'',
|
||||
'hallo@server.example',
|
||||
'hallo',
|
||||
'none',
|
||||
'',
|
||||
[],
|
||||
),
|
||||
RosterItem(
|
||||
1,
|
||||
'',
|
||||
'',
|
||||
'welt@different.server.example',
|
||||
'welt',
|
||||
'from',
|
||||
'',
|
||||
[ 'Friends' ],
|
||||
)
|
||||
];
|
||||
|
||||
group('Test roster pushes', () {
|
||||
test('Test removing an item', () async {
|
||||
var removeCalled = false;
|
||||
var addCalled = false;
|
||||
final result = await processRosterDiff(
|
||||
localRosterDouble,
|
||||
[
|
||||
XmppRosterItem(
|
||||
jid: 'hallo@server.example', subscription: 'remove',
|
||||
)
|
||||
],
|
||||
true,
|
||||
mkAddRosterItem((_) { addCalled = true; }),
|
||||
mkRosterUpdate(localRosterDouble),
|
||||
(jid) async {
|
||||
if (jid == 'hallo@server.example') {
|
||||
removeCalled = true;
|
||||
}
|
||||
},
|
||||
(_) async => null,
|
||||
(_, { String? id }) async {},
|
||||
);
|
||||
|
||||
expect(result.removed, [ 'hallo@server.example' ]);
|
||||
expect(result.modified.length, 0);
|
||||
expect(result.added.length, 0);
|
||||
expect(removeCalled, true);
|
||||
expect(addCalled, false);
|
||||
});
|
||||
|
||||
test('Test adding an item', () async {
|
||||
var removeCalled = false;
|
||||
var addCalled = false;
|
||||
final result = await processRosterDiff(
|
||||
localRosterSingle,
|
||||
[
|
||||
XmppRosterItem(
|
||||
jid: 'welt@different.server.example',
|
||||
subscription: 'from',
|
||||
)
|
||||
],
|
||||
true,
|
||||
mkAddRosterItem(
|
||||
(jid) {
|
||||
if (jid == 'welt@different.server.example') {
|
||||
addCalled = true;
|
||||
}
|
||||
}
|
||||
),
|
||||
mkRosterUpdate(localRosterSingle),
|
||||
(_) async { removeCalled = true; },
|
||||
(_) async => null,
|
||||
(_, { String? id }) async {},
|
||||
);
|
||||
|
||||
expect(result.removed, [ ]);
|
||||
expect(result.modified.length, 0);
|
||||
expect(result.added.length, 1);
|
||||
expect(result.added.first.subscription, 'from');
|
||||
expect(removeCalled, false);
|
||||
expect(addCalled, true);
|
||||
});
|
||||
|
||||
test('Test modifying an item', () async {
|
||||
var removeCalled = false;
|
||||
var addCalled = false;
|
||||
final result = await processRosterDiff(
|
||||
localRosterDouble,
|
||||
[
|
||||
XmppRosterItem(
|
||||
jid: 'welt@different.server.example',
|
||||
subscription: 'both',
|
||||
name: 'The World',
|
||||
)
|
||||
],
|
||||
true,
|
||||
mkAddRosterItem((_) { addCalled = false; }),
|
||||
mkRosterUpdate(localRosterDouble),
|
||||
(_) async { removeCalled = true; },
|
||||
(_) async => null,
|
||||
(_, { String? id }) async {},
|
||||
);
|
||||
|
||||
expect(result.removed, [ ]);
|
||||
expect(result.modified.length, 1);
|
||||
expect(result.added.length, 0);
|
||||
expect(result.modified.first.subscription, 'both');
|
||||
expect(result.modified.first.jid, 'welt@different.server.example');
|
||||
expect(result.modified.first.title, 'The World');
|
||||
expect(removeCalled, false);
|
||||
expect(addCalled, false);
|
||||
});
|
||||
});
|
||||
|
||||
group('Test roster requests', () {
|
||||
test('Test removing an item', () async {
|
||||
var removeCalled = false;
|
||||
var addCalled = false;
|
||||
final result = await processRosterDiff(
|
||||
localRosterSingle,
|
||||
[],
|
||||
false,
|
||||
mkAddRosterItem((_) { addCalled = true; }),
|
||||
mkRosterUpdate(localRosterDouble),
|
||||
(jid) async {
|
||||
if (jid == 'hallo@server.example') {
|
||||
removeCalled = true;
|
||||
}
|
||||
},
|
||||
(_) async => null,
|
||||
(_, { String? id }) async {},
|
||||
);
|
||||
|
||||
expect(result.removed, [ 'hallo@server.example' ]);
|
||||
expect(result.modified.length, 0);
|
||||
expect(result.added.length, 0);
|
||||
expect(removeCalled, true);
|
||||
expect(addCalled, false);
|
||||
});
|
||||
|
||||
test('Test adding an item', () async {
|
||||
var removeCalled = false;
|
||||
var addCalled = false;
|
||||
final result = await processRosterDiff(
|
||||
localRosterSingle,
|
||||
[
|
||||
XmppRosterItem(
|
||||
jid: 'hallo@server.example',
|
||||
name: 'hallo',
|
||||
subscription: 'none',
|
||||
),
|
||||
XmppRosterItem(
|
||||
jid: 'welt@different.server.example',
|
||||
subscription: 'both',
|
||||
)
|
||||
],
|
||||
false,
|
||||
mkAddRosterItem(
|
||||
(jid) {
|
||||
if (jid == 'welt@different.server.example') {
|
||||
addCalled = true;
|
||||
}
|
||||
}
|
||||
),
|
||||
mkRosterUpdate(localRosterSingle),
|
||||
(_) async { removeCalled = true; },
|
||||
(_) async => null,
|
||||
(_, { String? id }) async {},
|
||||
);
|
||||
|
||||
expect(result.removed, [ ]);
|
||||
expect(result.modified.length, 0);
|
||||
expect(result.added.length, 1);
|
||||
expect(result.added.first.subscription, 'both');
|
||||
expect(removeCalled, false);
|
||||
expect(addCalled, true);
|
||||
});
|
||||
|
||||
test('Test modifying an item', () async {
|
||||
var removeCalled = false;
|
||||
var addCalled = false;
|
||||
final result = await processRosterDiff(
|
||||
localRosterSingle,
|
||||
[
|
||||
XmppRosterItem(
|
||||
jid: 'hallo@server.example',
|
||||
subscription: 'both',
|
||||
name: 'Hallo Welt',
|
||||
)
|
||||
],
|
||||
false,
|
||||
mkAddRosterItem((_) { addCalled = false; }),
|
||||
mkRosterUpdate(localRosterDouble),
|
||||
(_) async { removeCalled = true; },
|
||||
(_) async => null,
|
||||
(_, { String? id }) async {},
|
||||
);
|
||||
|
||||
expect(result.removed, [ ]);
|
||||
expect(result.modified.length, 1);
|
||||
expect(result.added.length, 0);
|
||||
expect(result.modified.first.subscription, 'both');
|
||||
expect(result.modified.first.jid, 'hallo@server.example');
|
||||
expect(result.modified.first.title, 'Hallo Welt');
|
||||
expect(removeCalled, false);
|
||||
expect(addCalled, false);
|
||||
});
|
||||
});
|
||||
}
|
50
packages/moxxmpp/test/stanza_test.dart
Normal file
50
packages/moxxmpp/test/stanza_test.dart
Normal file
@ -0,0 +1,50 @@
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('Make sure reply does not copy the children', () {
|
||||
final stanza = Stanza.iq(
|
||||
to: 'hallo',
|
||||
from: 'world',
|
||||
id: 'abc123',
|
||||
type: 'get',
|
||||
children: [
|
||||
XMLNode(tag: 'test-tag'),
|
||||
XMLNode(tag: 'test-tag2')
|
||||
],
|
||||
);
|
||||
|
||||
final reply = stanza.reply();
|
||||
|
||||
expect(reply.children, []);
|
||||
expect(reply.type, 'result');
|
||||
expect(reply.from, stanza.to);
|
||||
expect(reply.to, stanza.from);
|
||||
expect(reply.id, stanza.id);
|
||||
});
|
||||
|
||||
test('Make sure reply includes the new children', () {
|
||||
final stanza = Stanza.iq(
|
||||
to: 'hallo',
|
||||
from: 'world',
|
||||
id: 'abc123',
|
||||
type: 'get',
|
||||
children: [
|
||||
XMLNode(tag: 'test-tag'),
|
||||
XMLNode(tag: 'test-tag2')
|
||||
],
|
||||
);
|
||||
|
||||
final reply = stanza.reply(
|
||||
children: [
|
||||
XMLNode.xmlns(
|
||||
tag: 'test',
|
||||
xmlns: 'test',
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
expect(reply.children.length, 1);
|
||||
expect(reply.firstTag('test') != null, true);
|
||||
});
|
||||
}
|
89
packages/moxxmpp/test/stanzahandler_test.dart
Normal file
89
packages/moxxmpp/test/stanzahandler_test.dart
Normal file
@ -0,0 +1,89 @@
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
final stanza1 = Stanza.iq(children: [
|
||||
XMLNode.xmlns(tag: 'tag', xmlns: 'owo')
|
||||
],);
|
||||
final stanza2 = Stanza.message(children: [
|
||||
XMLNode.xmlns(tag: 'some-other-tag', xmlns: 'owo')
|
||||
],);
|
||||
|
||||
void main() {
|
||||
test('match all', () {
|
||||
final handler = StanzaHandler(callback: (stanza, _) async => StanzaHandlerData(true, false, null, stanza));
|
||||
|
||||
expect(handler.matches(Stanza.iq()), true);
|
||||
expect(handler.matches(Stanza.message()), true);
|
||||
expect(handler.matches(Stanza.presence()), true);
|
||||
expect(handler.matches(stanza1), true);
|
||||
expect(handler.matches(stanza2), true);
|
||||
});
|
||||
test('xmlns matching', () {
|
||||
final handler = StanzaHandler(
|
||||
callback: (stanza, _) async => StanzaHandlerData(true, false, null, stanza),
|
||||
tagXmlns: 'owo',
|
||||
);
|
||||
|
||||
expect(handler.matches(Stanza.iq()), false);
|
||||
expect(handler.matches(Stanza.message()), false);
|
||||
expect(handler.matches(Stanza.presence()), false);
|
||||
expect(handler.matches(stanza1), true);
|
||||
expect(handler.matches(stanza2), true);
|
||||
});
|
||||
test('stanzaTag matching', () {
|
||||
var run = false;
|
||||
final handler = StanzaHandler(callback: (stanza, _) async {
|
||||
run = true;
|
||||
return StanzaHandlerData(true, false, null, stanza);
|
||||
}, stanzaTag: 'iq',);
|
||||
|
||||
expect(handler.matches(Stanza.iq()), true);
|
||||
expect(handler.matches(Stanza.message()), false);
|
||||
expect(handler.matches(Stanza.presence()), false);
|
||||
expect(handler.matches(stanza1), true);
|
||||
expect(handler.matches(stanza2), false);
|
||||
|
||||
handler.callback(stanza2, StanzaHandlerData(false, false, null, stanza2));
|
||||
expect(run, true);
|
||||
});
|
||||
test('tagName matching', () {
|
||||
final handler = StanzaHandler(
|
||||
callback: (stanza, _) async => StanzaHandlerData(true, false, null, stanza),
|
||||
tagName: 'tag',
|
||||
);
|
||||
|
||||
expect(handler.matches(Stanza.iq()), false);
|
||||
expect(handler.matches(Stanza.message()), false);
|
||||
expect(handler.matches(Stanza.presence()), false);
|
||||
expect(handler.matches(stanza1), true);
|
||||
expect(handler.matches(stanza2), false);
|
||||
});
|
||||
test('combined matching', () {
|
||||
final handler = StanzaHandler(
|
||||
callback: (stanza, _) async => StanzaHandlerData(true, false, null, stanza),
|
||||
tagName: 'tag',
|
||||
stanzaTag: 'iq',
|
||||
tagXmlns: 'owo',
|
||||
);
|
||||
|
||||
expect(handler.matches(Stanza.iq()), false);
|
||||
expect(handler.matches(Stanza.message()), false);
|
||||
expect(handler.matches(Stanza.presence()), false);
|
||||
expect(handler.matches(stanza1), true);
|
||||
expect(handler.matches(stanza2), false);
|
||||
});
|
||||
|
||||
test('sorting', () {
|
||||
final handlerList = [
|
||||
StanzaHandler(callback: (stanza, _) async => StanzaHandlerData(true, false, null, stanza), tagName: '1', priority: 100),
|
||||
StanzaHandler(callback: (stanza, _) async => StanzaHandlerData(true, false, null, stanza), tagName: '2'),
|
||||
StanzaHandler(callback: (stanza, _) async => StanzaHandlerData(true, false, null, stanza), tagName: '3', priority: 50)
|
||||
];
|
||||
|
||||
handlerList.sort(stanzaHandlerSortComparator);
|
||||
|
||||
expect(handlerList[0].tagName, '1');
|
||||
expect(handlerList[1].tagName, '3');
|
||||
expect(handlerList[2].tagName, '2');
|
||||
});
|
||||
}
|
32
packages/moxxmpp/test/stringxml_test.dart
Normal file
32
packages/moxxmpp/test/stringxml_test.dart
Normal file
@ -0,0 +1,32 @@
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:xml/xml.dart';
|
||||
import 'helpers/xml.dart';
|
||||
|
||||
void main() {
|
||||
test('Test stringxml', () {
|
||||
final child = XMLNode(tag: 'uwu', attributes: { 'strength': 10 });
|
||||
final stanza = XMLNode.xmlns(tag: 'uwu-meter', xmlns: 'uwu', children: [ child ]);
|
||||
expect(XMLNode(tag: 'iq', attributes: {'xmlns': 'uwu'}).toXml(), "<iq xmlns='uwu' />");
|
||||
expect(XMLNode.xmlns(tag: 'iq', xmlns: 'uwu', attributes: {'how': 'uwu'}).toXml(), "<iq xmlns='uwu' how='uwu' />");
|
||||
expect(stanza.toXml(), "<uwu-meter xmlns='uwu'><uwu strength=10 /></uwu-meter>");
|
||||
|
||||
expect(StreamHeaderNonza('uwu.server').toXml(), "<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='uwu.server' xml:lang='en'>");
|
||||
|
||||
expect(XMLNode(tag: 'text', attributes: {}, text: 'hallo').toXml(), '<text>hallo</text>');
|
||||
expect(XMLNode(tag: 'text', attributes: { 'world': 'no' }, text: 'hallo').toXml(), "<text world='no'>hallo</text>");
|
||||
expect(XMLNode(tag: 'text', attributes: {}, text: 'hallo').toXml(), '<text>hallo</text>');
|
||||
expect(XMLNode(tag: 'text', attributes: {}, text: 'test').innerText(), 'test');
|
||||
});
|
||||
|
||||
test('Test XmlElement', () {
|
||||
expect(XMLNode.fromXmlElement(XmlDocument.parse("<root owo='uwu' />").firstElementChild!).toXml(), "<root owo='uwu' />");
|
||||
});
|
||||
|
||||
test('Test the find functions', () {
|
||||
final node1 = XMLNode.fromString('<message><a xmlns="a" /><body>Hallo</body></message>');
|
||||
|
||||
expect(compareXMLNodes(node1.firstTag('body')!, XMLNode.fromString('<body>Hallo</body>')), true);
|
||||
expect(compareXMLNodes(node1.firstTagByXmlns('a')!, XMLNode.fromString('<a xmlns="a" />')), true);
|
||||
});
|
||||
}
|
16
packages/moxxmpp/test/xeps/xep_0004_test.dart
Normal file
16
packages/moxxmpp/test/xeps/xep_0004_test.dart
Normal file
@ -0,0 +1,16 @@
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('Parsing', () {
|
||||
const testData = "<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>urn:xmpp:dataforms:softwareinfo</value></field><field var='ip_version' type='text-multi' ><value>ipv4</value><value>ipv6</value></field><field var='os'><value>Mac</value></field><field var='os_version'><value>10.5.1</value></field><field var='software'><value>Psi</value></field><field var='software_version'><value>0.11</value></field></x>";
|
||||
|
||||
final form = parseDataForm(XMLNode.fromString(testData));
|
||||
expect(form.getFieldByVar('FORM_TYPE')?.values.first, 'urn:xmpp:dataforms:softwareinfo');
|
||||
expect(form.getFieldByVar('ip_version')?.values, [ 'ipv4', 'ipv6' ]);
|
||||
expect(form.getFieldByVar('os')?.values.first, 'Mac');
|
||||
expect(form.getFieldByVar('os_version')?.values.first, '10.5.1');
|
||||
expect(form.getFieldByVar('software')?.values.first, 'Psi');
|
||||
expect(form.getFieldByVar('software_version')?.values.first, '0.11');
|
||||
});
|
||||
}
|
111
packages/moxxmpp/test/xeps/xep_0030.dart
Normal file
111
packages/moxxmpp/test/xeps/xep_0030.dart
Normal file
@ -0,0 +1,111 @@
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../helpers/xmpp.dart';
|
||||
|
||||
void main() {
|
||||
test('Test having multiple disco requests for the same JID', () async {
|
||||
final fakeSocket = StubTCPSocket(
|
||||
play: [
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||
<mechanism>PLAIN</mechanism>
|
||||
</mechanisms>
|
||||
</stream:features>''',
|
||||
),
|
||||
StringExpectation(
|
||||
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />'
|
||||
),
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
|
||||
<optional/>
|
||||
</session>
|
||||
<csi xmlns="urn:xmpp:csi:0"/>
|
||||
<sm xmlns="urn:xmpp:sm:3"/>
|
||||
</stream:features>
|
||||
''',
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
|
||||
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||
ignoreId: true,
|
||||
),
|
||||
StringExpectation(
|
||||
"<presence xmlns='jabber:client' from='polynomdivision@test.server/MU29eEZn'><show>chat</show><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://moxxy.im' ver='QRTBC5cg/oYd+UOTYazSQR4zb/I=' /></presence>",
|
||||
'',
|
||||
),
|
||||
StanzaExpectation(
|
||||
"<iq type='get' id='ec325efc-9924-4c48-93f8-ed34a2b0e5fc' to='romeo@montague.lit/orchard' from='polynomdivision@test.server/MU29eEZn' xmlns='jabber:client'><query xmlns='http://jabber.org/protocol/disco#info' /></iq>",
|
||||
'',
|
||||
ignoreId: true,
|
||||
adjustId: false,
|
||||
),
|
||||
|
||||
],
|
||||
);
|
||||
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), socket: fakeSocket);
|
||||
conn.setConnectionSettings(ConnectionSettings(
|
||||
jid: JID.fromString('polynomdivision@test.server'),
|
||||
password: 'aaaa',
|
||||
useDirectTLS: true,
|
||||
allowPlainAuth: true,
|
||||
),);
|
||||
conn.registerManagers([
|
||||
PresenceManager(),
|
||||
RosterManager(),
|
||||
DiscoManager(),
|
||||
PingManager(),
|
||||
]);
|
||||
conn.registerFeatureNegotiators(
|
||||
[
|
||||
SaslPlainNegotiator(),
|
||||
SaslScramNegotiator(10, '', '', ScramHashType.sha512),
|
||||
ResourceBindingNegotiator(),
|
||||
]
|
||||
);
|
||||
|
||||
final disco = conn.getManagerById<DiscoManager>(discoManager)!;
|
||||
|
||||
await conn.connect();
|
||||
await Future.delayed(const Duration(seconds: 3));
|
||||
|
||||
final jid = JID.fromString('romeo@montague.lit/orchard');
|
||||
final result1 = disco.discoInfoQuery(jid.toString());
|
||||
final result2 = disco.discoInfoQuery(jid.toString());
|
||||
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
expect(
|
||||
disco.getRunningInfoQueries(DiscoCacheKey(jid.toString(), null)).length,
|
||||
1,
|
||||
);
|
||||
fakeSocket.injectRawXml("<iq type='result' id='${fakeSocket.lastId!}' from='romeo@montague.lit/orchard' to='polynomdivision@test.server/MU29eEZn' xmlns='jabber:client'><query xmlns='http://jabber.org/protocol/disco#info' /></iq>");
|
||||
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
|
||||
expect(fakeSocket.getState(), 6);
|
||||
expect(await result1, await result2);
|
||||
expect(disco.hasInfoQueriesRunning(), false);
|
||||
});
|
||||
}
|
167
packages/moxxmpp/test/xeps/xep_0115_test.dart
Normal file
167
packages/moxxmpp/test/xeps/xep_0115_test.dart
Normal file
@ -0,0 +1,167 @@
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('Test XEP example', () async {
|
||||
final data = DiscoInfo(
|
||||
[
|
||||
'http://jabber.org/protocol/caps',
|
||||
'http://jabber.org/protocol/disco#info',
|
||||
'http://jabber.org/protocol/disco#items',
|
||||
'http://jabber.org/protocol/muc'
|
||||
],
|
||||
[
|
||||
Identity(
|
||||
category: 'client',
|
||||
type: 'pc',
|
||||
name: 'Exodus 0.9.1',
|
||||
)
|
||||
],
|
||||
[],
|
||||
JID.fromString('some@user.local/test'),
|
||||
);
|
||||
|
||||
final hash = await calculateCapabilityHash(data, Sha1());
|
||||
expect(hash, 'QgayPKawpkPSDYmwT/WM94uAlu0=');
|
||||
});
|
||||
|
||||
test('Test complex generation example', () async {
|
||||
const extDiscoDataString = "<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>urn:xmpp:dataforms:softwareinfo</value></field><field var='ip_version' type='text-multi' ><value>ipv4</value><value>ipv6</value></field><field var='os'><value>Mac</value></field><field var='os_version'><value>10.5.1</value></field><field var='software'><value>Psi</value></field><field var='software_version'><value>0.11</value></field></x>";
|
||||
final data = DiscoInfo(
|
||||
[
|
||||
'http://jabber.org/protocol/caps',
|
||||
'http://jabber.org/protocol/disco#info',
|
||||
'http://jabber.org/protocol/disco#items',
|
||||
'http://jabber.org/protocol/muc'
|
||||
],
|
||||
[
|
||||
const Identity(
|
||||
category: 'client',
|
||||
type: 'pc',
|
||||
name: 'Psi 0.11',
|
||||
lang: 'en',
|
||||
),
|
||||
const Identity(
|
||||
category: 'client',
|
||||
type: 'pc',
|
||||
name: 'Ψ 0.11',
|
||||
lang: 'el',
|
||||
),
|
||||
],
|
||||
[ parseDataForm(XMLNode.fromString(extDiscoDataString)) ],
|
||||
JID.fromString('some@user.local/test'),
|
||||
);
|
||||
|
||||
final hash = await calculateCapabilityHash(data, Sha1());
|
||||
expect(hash, 'q07IKJEyjvHSyhy//CH0CxmKi8w=');
|
||||
});
|
||||
|
||||
test('Test Gajim capability hash computation', () async {
|
||||
// TODO: This one fails
|
||||
/*
|
||||
final data = DiscoInfo(
|
||||
features: [
|
||||
"http://jabber.org/protocol/bytestreams",
|
||||
"http://jabber.org/protocol/muc",
|
||||
"http://jabber.org/protocol/commands",
|
||||
"http://jabber.org/protocol/disco#info",
|
||||
"jabber:iq:last",
|
||||
"jabber:x:data",
|
||||
"jabber:x:encrypted",
|
||||
"urn:xmpp:ping",
|
||||
"http://jabber.org/protocol/chatstates",
|
||||
"urn:xmpp:receipts",
|
||||
"urn:xmpp:time",
|
||||
"jabber:iq:version",
|
||||
"http://jabber.org/protocol/rosterx",
|
||||
"urn:xmpp:sec-label:0",
|
||||
"jabber:x:conference",
|
||||
"urn:xmpp:message-correct:0",
|
||||
"urn:xmpp:chat-markers:0",
|
||||
"urn:xmpp:eme:0",
|
||||
"http://jabber.org/protocol/xhtml-im",
|
||||
"urn:xmpp:hashes:2",
|
||||
"urn:xmpp:hash-function-text-names:md5",
|
||||
"urn:xmpp:hash-function-text-names:sha-1",
|
||||
"urn:xmpp:hash-function-text-names:sha-256",
|
||||
"urn:xmpp:hash-function-text-names:sha-512",
|
||||
"urn:xmpp:hash-function-text-names:sha3-256",
|
||||
"urn:xmpp:hash-function-text-names:sha3-512",
|
||||
"urn:xmpp:hash-function-text-names:id-blake2b256",
|
||||
"urn:xmpp:hash-function-text-names:id-blake2b512",
|
||||
"urn:xmpp:jingle:1",
|
||||
"urn:xmpp:jingle:apps:file-transfer:5",
|
||||
"urn:xmpp:jingle:security:xtls:0",
|
||||
"urn:xmpp:jingle:transports:s5b:1",
|
||||
"urn:xmpp:jingle:transports:ibb:1",
|
||||
"urn:xmpp:avatar:metadata+notify",
|
||||
"urn:xmpp:message-moderate:0",
|
||||
"http://jabber.org/protocol/tune+notify",
|
||||
"http://jabber.org/protocol/geoloc+notify",
|
||||
"http://jabber.org/protocol/nick+notify",
|
||||
"eu.siacs.conversations.axolotl.devicelist+notify",
|
||||
],
|
||||
identities: [
|
||||
Identity(
|
||||
category: "client",
|
||||
type: "pc",
|
||||
name: "Gajim"
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
final hash = await calculateCapabilityHash(data, Sha1());
|
||||
expect(hash, "T7fOZrtBnV8sDA2fFTS59vyOyUs=");
|
||||
*/
|
||||
});
|
||||
|
||||
test('Test Conversations hash computation', () async {
|
||||
final data = DiscoInfo(
|
||||
[
|
||||
'eu.siacs.conversations.axolotl.devicelist+notify',
|
||||
'http://jabber.org/protocol/caps',
|
||||
'http://jabber.org/protocol/chatstates',
|
||||
'http://jabber.org/protocol/disco#info',
|
||||
'http://jabber.org/protocol/muc',
|
||||
'http://jabber.org/protocol/nick+notify',
|
||||
'jabber:iq:version',
|
||||
'jabber:x:conference',
|
||||
'jabber:x:oob',
|
||||
'storage:bookmarks+notify',
|
||||
'urn:xmpp:avatar:metadata+notify',
|
||||
'urn:xmpp:chat-markers:0',
|
||||
'urn:xmpp:jingle-message:0',
|
||||
'urn:xmpp:jingle:1',
|
||||
'urn:xmpp:jingle:apps:dtls:0',
|
||||
'urn:xmpp:jingle:apps:file-transfer:3',
|
||||
'urn:xmpp:jingle:apps:file-transfer:4',
|
||||
'urn:xmpp:jingle:apps:file-transfer:5',
|
||||
'urn:xmpp:jingle:apps:rtp:1',
|
||||
'urn:xmpp:jingle:apps:rtp:audio',
|
||||
'urn:xmpp:jingle:apps:rtp:video',
|
||||
'urn:xmpp:jingle:jet-omemo:0',
|
||||
'urn:xmpp:jingle:jet:0',
|
||||
'urn:xmpp:jingle:transports:ibb:1',
|
||||
'urn:xmpp:jingle:transports:ice-udp:1',
|
||||
'urn:xmpp:jingle:transports:s5b:1',
|
||||
'urn:xmpp:message-correct:0',
|
||||
'urn:xmpp:ping',
|
||||
'urn:xmpp:receipts',
|
||||
'urn:xmpp:time'
|
||||
],
|
||||
[
|
||||
Identity(
|
||||
category: 'client',
|
||||
type: 'phone',
|
||||
name: 'Conversations',
|
||||
)
|
||||
],
|
||||
[],
|
||||
JID.fromString('user@server.local/test'),
|
||||
);
|
||||
|
||||
final hash = await calculateCapabilityHash(data, Sha1());
|
||||
expect(hash, 'zcIke+Rk13ah4d1pwDG7bEZsVwA=');
|
||||
});
|
||||
}
|
737
packages/moxxmpp/test/xeps/xep_0198_test.dart
Normal file
737
packages/moxxmpp/test/xeps/xep_0198_test.dart
Normal file
@ -0,0 +1,737 @@
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
import '../helpers/logging.dart';
|
||||
import '../helpers/xmpp.dart';
|
||||
|
||||
Future<void> runIncomingStanzaHandlers(StreamManagementManager man, Stanza stanza) async {
|
||||
for (final handler in man.getIncomingStanzaHandlers()) {
|
||||
if (handler.matches(stanza)) await handler.callback(stanza, StanzaHandlerData(false, false, null, stanza));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> runOutgoingStanzaHandlers(StreamManagementManager man, Stanza stanza) async {
|
||||
for (final handler in man.getOutgoingPostStanzaHandlers()) {
|
||||
if (handler.matches(stanza)) await handler.callback(stanza, StanzaHandlerData(false, false, null, stanza));
|
||||
}
|
||||
}
|
||||
|
||||
XmppManagerAttributes mkAttributes(void Function(Stanza) callback) {
|
||||
return XmppManagerAttributes(
|
||||
sendStanza: (stanza, { StanzaFromType addFrom = StanzaFromType.full, bool addId = true, bool awaitable = true, bool encrypted = false }) async {
|
||||
callback(stanza);
|
||||
|
||||
return Stanza.message();
|
||||
},
|
||||
sendNonza: (nonza) {},
|
||||
sendEvent: (event) {},
|
||||
getManagerById: getManagerNullStub,
|
||||
getConnectionSettings: () => ConnectionSettings(
|
||||
jid: JID.fromString('hallo@example.server'),
|
||||
password: 'password',
|
||||
useDirectTLS: true,
|
||||
allowPlainAuth: false,
|
||||
),
|
||||
isFeatureSupported: (_) => false,
|
||||
getFullJID: () => JID.fromString('hallo@example.server/uwu'),
|
||||
getSocket: () => StubTCPSocket(play: []),
|
||||
getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])),
|
||||
getNegotiatorById: getNegotiatorNullStub,
|
||||
);
|
||||
}
|
||||
|
||||
XMLNode mkAck(int h) => XMLNode.xmlns(tag: 'a', xmlns: 'urn:xmpp:sm:3', attributes: { 'h': h.toString() });
|
||||
|
||||
void main() {
|
||||
initLogger();
|
||||
|
||||
final stanza = Stanza(
|
||||
to: 'some.user@server.example',
|
||||
tag: 'message',
|
||||
);
|
||||
|
||||
test('Test stream with SM enablement', () async {
|
||||
final attributes = mkAttributes((_) {});
|
||||
final manager = StreamManagementManager();
|
||||
manager.register(attributes);
|
||||
|
||||
// [...]
|
||||
// <enable /> // <enabled />
|
||||
await manager.onXmppEvent(StreamManagementEnabledEvent(resource: 'hallo'));
|
||||
expect(manager.state.c2s, 0);
|
||||
expect(manager.state.s2c, 0);
|
||||
|
||||
expect(manager.isStreamManagementEnabled(), true);
|
||||
|
||||
// Send a stanza 5 times
|
||||
for (var i = 0; i < 5; i++) {
|
||||
await runOutgoingStanzaHandlers(manager, stanza);
|
||||
}
|
||||
expect(manager.state.c2s, 5);
|
||||
|
||||
// Receive 3 stanzas
|
||||
for (var i = 0; i < 3; i++) {
|
||||
await runIncomingStanzaHandlers(manager, stanza);
|
||||
}
|
||||
expect(manager.state.s2c, 3);
|
||||
});
|
||||
|
||||
group('Acking', () {
|
||||
test('Test completely clearing the queue', () async {
|
||||
final attributes = mkAttributes((_) {});
|
||||
final manager = StreamManagementManager();
|
||||
manager.register(attributes);
|
||||
|
||||
await manager.onXmppEvent(StreamManagementEnabledEvent(resource: 'hallo'));
|
||||
|
||||
// Send a stanza 5 times
|
||||
for (var i = 0; i < 5; i++) {
|
||||
await runOutgoingStanzaHandlers(manager, stanza);
|
||||
}
|
||||
|
||||
// <a h='5'/>
|
||||
await manager.runNonzaHandlers(mkAck(5));
|
||||
expect(manager.getUnackedStanzas().length, 0);
|
||||
});
|
||||
test('Test partially clearing the queue', () async {
|
||||
final attributes = mkAttributes((_) {});
|
||||
final manager = StreamManagementManager();
|
||||
manager.register(attributes);
|
||||
|
||||
await manager.onXmppEvent(StreamManagementEnabledEvent(resource: 'hallo'));
|
||||
|
||||
// Send a stanza 5 times
|
||||
for (var i = 0; i < 5; i++) {
|
||||
await runOutgoingStanzaHandlers(manager, stanza);
|
||||
}
|
||||
|
||||
// <a h='3'/>
|
||||
await manager.runNonzaHandlers(mkAck(3));
|
||||
expect(manager.getUnackedStanzas().length, 2);
|
||||
});
|
||||
test('Send an ack with h > c2s', () async {
|
||||
final attributes = mkAttributes((_) {});
|
||||
final manager = StreamManagementManager();
|
||||
manager.register(attributes);
|
||||
|
||||
await manager.onXmppEvent(StreamManagementEnabledEvent(resource: 'hallo'));
|
||||
|
||||
// Send a stanza 5 times
|
||||
for (var i = 0; i < 5; i++) {
|
||||
await runOutgoingStanzaHandlers(manager, stanza);
|
||||
}
|
||||
|
||||
// <a h='3'/>
|
||||
await manager.runNonzaHandlers(mkAck(6));
|
||||
expect(manager.getUnackedStanzas().length, 0);
|
||||
expect(manager.state.c2s, 6);
|
||||
});
|
||||
test('Send an ack with h < c2s', () async {
|
||||
final attributes = mkAttributes((_) {});
|
||||
final manager = StreamManagementManager();
|
||||
manager.register(attributes);
|
||||
|
||||
await manager.onXmppEvent(StreamManagementEnabledEvent(resource: 'hallo'));
|
||||
|
||||
// Send a stanza 5 times
|
||||
for (var i = 0; i < 5; i++) {
|
||||
await runOutgoingStanzaHandlers(manager, stanza);
|
||||
}
|
||||
|
||||
// <a h='3'/>
|
||||
await manager.runNonzaHandlers(mkAck(3));
|
||||
expect(manager.getUnackedStanzas().length, 2);
|
||||
expect(manager.state.c2s, 5);
|
||||
});
|
||||
});
|
||||
|
||||
group('Counting acks', () {
|
||||
test('Sending all pending acks at once', () async {
|
||||
final attributes = mkAttributes((_) {});
|
||||
final manager = StreamManagementManager();
|
||||
manager.register(attributes);
|
||||
await manager.onXmppEvent(StreamManagementEnabledEvent(resource: 'hallo'));
|
||||
|
||||
// Send a stanza 5 times
|
||||
for (var i = 0; i < 5; i++) {
|
||||
await runOutgoingStanzaHandlers(manager, stanza);
|
||||
}
|
||||
expect(await manager.getPendingAcks(), 5);
|
||||
|
||||
// Ack all of them at once
|
||||
await manager.runNonzaHandlers(mkAck(5));
|
||||
expect(await manager.getPendingAcks(), 0);
|
||||
});
|
||||
test('Sending partial pending acks at once', () async {
|
||||
final attributes = mkAttributes((_) {});
|
||||
final manager = StreamManagementManager();
|
||||
manager.register(attributes);
|
||||
await manager.onXmppEvent(StreamManagementEnabledEvent(resource: 'hallo'));
|
||||
|
||||
// Send a stanza 5 times
|
||||
for (var i = 0; i < 5; i++) {
|
||||
await runOutgoingStanzaHandlers(manager, stanza);
|
||||
}
|
||||
expect(await manager.getPendingAcks(), 5);
|
||||
|
||||
// Ack only 3 of them at once
|
||||
await manager.runNonzaHandlers(mkAck(3));
|
||||
expect(await manager.getPendingAcks(), 2);
|
||||
});
|
||||
|
||||
test('Test counting incoming stanzas for which handlers end early', () async {
|
||||
final fakeSocket = StubTCPSocket(
|
||||
play: [
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||
<mechanism>PLAIN</mechanism>
|
||||
</mechanisms>
|
||||
</stream:features>''',
|
||||
),
|
||||
StringExpectation(
|
||||
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />'
|
||||
),
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
|
||||
<optional/>
|
||||
</session>
|
||||
<csi xmlns="urn:xmpp:csi:0"/>
|
||||
<sm xmlns="urn:xmpp:sm:3"/>
|
||||
</stream:features>
|
||||
''',
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
|
||||
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||
ignoreId: true,
|
||||
),
|
||||
StringExpectation(
|
||||
"<enable xmlns='urn:xmpp:sm:3' resume='true' />",
|
||||
'<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true" />',
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket);
|
||||
conn.setConnectionSettings(ConnectionSettings(
|
||||
jid: JID.fromString('polynomdivision@test.server'),
|
||||
password: 'aaaa',
|
||||
useDirectTLS: true,
|
||||
allowPlainAuth: true,
|
||||
),);
|
||||
final sm = StreamManagementManager();
|
||||
conn.registerManagers([
|
||||
PresenceManager(),
|
||||
RosterManager(),
|
||||
DiscoManager(),
|
||||
PingManager(),
|
||||
sm,
|
||||
CarbonsManager()..forceEnable(),
|
||||
]);
|
||||
conn.registerFeatureNegotiators(
|
||||
[
|
||||
SaslPlainNegotiator(),
|
||||
ResourceBindingNegotiator(),
|
||||
StreamManagementNegotiator(),
|
||||
]
|
||||
);
|
||||
|
||||
await conn.connect();
|
||||
await Future.delayed(const Duration(seconds: 3));
|
||||
expect(fakeSocket.getState(), 6);
|
||||
expect(await conn.getConnectionState(), XmppConnectionState.connected);
|
||||
expect(
|
||||
conn.getManagerById<StreamManagementManager>(smManager)!.isStreamManagementEnabled(),
|
||||
true,
|
||||
);
|
||||
|
||||
// Send an invalid carbon
|
||||
fakeSocket.injectRawXml('''
|
||||
<message xmlns='jabber:client'
|
||||
from='romeo@montague.example'
|
||||
to='romeo@montague.example/home'
|
||||
type='chat'>
|
||||
<received xmlns='urn:xmpp:carbons:2'>
|
||||
<forwarded xmlns='urn:xmpp:forward:0'>
|
||||
<message xmlns='jabber:client'
|
||||
from='juliet@capulet.example/balcony'
|
||||
to='romeo@montague.example/garden'
|
||||
type='chat'>
|
||||
<body>What man art thou that, thus bescreen'd in night, so stumblest on my counsel?</body>
|
||||
<thread>0e3141cd80894871a68e6fe6b1ec56fa</thread>
|
||||
</message>
|
||||
</forwarded>
|
||||
</received>
|
||||
</message>
|
||||
''');
|
||||
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
expect(sm.state.s2c, 1);
|
||||
});
|
||||
|
||||
test('Test counting incoming stanzas that are awaited', () async {
|
||||
final fakeSocket = StubTCPSocket(
|
||||
play: [
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||
<mechanism>PLAIN</mechanism>
|
||||
</mechanisms>
|
||||
</stream:features>''',
|
||||
),
|
||||
StringExpectation(
|
||||
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />'
|
||||
),
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
|
||||
<optional/>
|
||||
</session>
|
||||
<csi xmlns="urn:xmpp:csi:0"/>
|
||||
<sm xmlns="urn:xmpp:sm:3"/>
|
||||
</stream:features>
|
||||
''',
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
|
||||
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||
ignoreId: true,
|
||||
),
|
||||
StringExpectation(
|
||||
"<enable xmlns='urn:xmpp:sm:3' resume='true' />",
|
||||
'<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true" />',
|
||||
),
|
||||
StringExpectation(
|
||||
"<presence xmlns='jabber:client' from='polynomdivision@test.server/MU29eEZn'><show>chat</show><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://moxxy.im' ver='QRTBC5cg/oYd+UOTYazSQR4zb/I=' /></presence>",
|
||||
'<iq type="result" />',
|
||||
),
|
||||
StanzaExpectation(
|
||||
"<iq to='user@example.com' type='get' id='a' xmlns='jabber:client' />",
|
||||
"<iq to='user@example.com' type='result' id='a' />",
|
||||
ignoreId: true,
|
||||
adjustId: true,
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket);
|
||||
conn.setConnectionSettings(ConnectionSettings(
|
||||
jid: JID.fromString('polynomdivision@test.server'),
|
||||
password: 'aaaa',
|
||||
useDirectTLS: true,
|
||||
allowPlainAuth: true,
|
||||
),);
|
||||
final sm = StreamManagementManager();
|
||||
conn.registerManagers([
|
||||
PresenceManager(),
|
||||
RosterManager(),
|
||||
DiscoManager(),
|
||||
PingManager(),
|
||||
sm,
|
||||
CarbonsManager()..forceEnable(),
|
||||
]);
|
||||
conn.registerFeatureNegotiators(
|
||||
[
|
||||
SaslPlainNegotiator(),
|
||||
ResourceBindingNegotiator(),
|
||||
StreamManagementNegotiator(),
|
||||
]
|
||||
);
|
||||
|
||||
await conn.connect();
|
||||
await Future.delayed(const Duration(seconds: 3));
|
||||
expect(fakeSocket.getState(), 6);
|
||||
expect(await conn.getConnectionState(), XmppConnectionState.connected);
|
||||
expect(
|
||||
conn.getManagerById<StreamManagementManager>(smManager)!.isStreamManagementEnabled(),
|
||||
true,
|
||||
);
|
||||
|
||||
// Await an iq
|
||||
await conn.sendStanza(
|
||||
Stanza.iq(
|
||||
to: 'user@example.com',
|
||||
type: 'get',
|
||||
),
|
||||
addFrom: StanzaFromType.none,
|
||||
);
|
||||
|
||||
expect(sm.state.s2c, 2);
|
||||
});
|
||||
});
|
||||
|
||||
group('Stream resumption', () {
|
||||
test('Stanza retransmission', () async {
|
||||
var stanzaCount = 0;
|
||||
final attributes = mkAttributes((_) {
|
||||
stanzaCount++;
|
||||
});
|
||||
final manager = StreamManagementManager();
|
||||
manager.register(attributes);
|
||||
|
||||
await manager.onXmppEvent(StreamManagementEnabledEvent(resource: 'hallo'));
|
||||
|
||||
// Send 5 stanzas
|
||||
for (var i = 0; i < 5; i++) {
|
||||
await runOutgoingStanzaHandlers(manager, stanza);
|
||||
}
|
||||
|
||||
// Only ack 3
|
||||
// <a h='3' />
|
||||
await manager.runNonzaHandlers(mkAck(3));
|
||||
expect(manager.getUnackedStanzas().length, 2);
|
||||
|
||||
// Lose connection
|
||||
// [ Reconnect ]
|
||||
await manager.onXmppEvent(StreamResumedEvent(h: 3));
|
||||
|
||||
expect(stanzaCount, 2);
|
||||
});
|
||||
test('Resumption with prior state', () async {
|
||||
var stanzaCount = 0;
|
||||
final attributes = mkAttributes((_) {
|
||||
stanzaCount++;
|
||||
});
|
||||
final manager = StreamManagementManager();
|
||||
manager.register(attributes);
|
||||
|
||||
// [ ... ]
|
||||
await manager.onXmppEvent(StreamManagementEnabledEvent(resource: 'hallo'));
|
||||
manager.setState(manager.state.copyWith(c2s: 150, s2c: 70));
|
||||
|
||||
// Send some stanzas but don't ack them
|
||||
for (var i = 0; i < 5; i++) {
|
||||
await runOutgoingStanzaHandlers(manager, stanza);
|
||||
}
|
||||
expect(manager.getUnackedStanzas().length, 5);
|
||||
|
||||
// Lose connection
|
||||
// [ Reconnect ]
|
||||
await manager.onXmppEvent(StreamResumedEvent(h: 150));
|
||||
expect(manager.getUnackedStanzas().length, 0);
|
||||
expect(stanzaCount, 5);
|
||||
});
|
||||
});
|
||||
|
||||
group('Test the negotiator', () {
|
||||
test('Test successful stream enablement', () async {
|
||||
final fakeSocket = StubTCPSocket(
|
||||
play: [
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||
<mechanism>PLAIN</mechanism>
|
||||
</mechanisms>
|
||||
</stream:features>''',
|
||||
),
|
||||
StringExpectation(
|
||||
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />'
|
||||
),
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
|
||||
<optional/>
|
||||
</session>
|
||||
<csi xmlns="urn:xmpp:csi:0"/>
|
||||
<sm xmlns="urn:xmpp:sm:3"/>
|
||||
</stream:features>
|
||||
''',
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
|
||||
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||
ignoreId: true
|
||||
),
|
||||
StringExpectation(
|
||||
"<enable xmlns='urn:xmpp:sm:3' resume='true' />",
|
||||
'<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true" />'
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket);
|
||||
conn.setConnectionSettings(ConnectionSettings(
|
||||
jid: JID.fromString('polynomdivision@test.server'),
|
||||
password: 'aaaa',
|
||||
useDirectTLS: true,
|
||||
allowPlainAuth: true,
|
||||
),);
|
||||
conn.registerManagers([
|
||||
PresenceManager(),
|
||||
RosterManager(),
|
||||
DiscoManager(),
|
||||
PingManager(),
|
||||
StreamManagementManager(),
|
||||
]);
|
||||
conn.registerFeatureNegotiators(
|
||||
[
|
||||
SaslPlainNegotiator(),
|
||||
ResourceBindingNegotiator(),
|
||||
StreamManagementNegotiator(),
|
||||
]
|
||||
);
|
||||
|
||||
await conn.connect();
|
||||
await Future.delayed(const Duration(seconds: 3));
|
||||
|
||||
expect(fakeSocket.getState(), 6);
|
||||
expect(await conn.getConnectionState(), XmppConnectionState.connected);
|
||||
expect(
|
||||
conn.getManagerById<StreamManagementManager>(smManager)!.isStreamManagementEnabled(),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test('Test a failed stream resumption', () async {
|
||||
final fakeSocket = StubTCPSocket(
|
||||
play: [
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||
<mechanism>PLAIN</mechanism>
|
||||
</mechanisms>
|
||||
</stream:features>''',
|
||||
),
|
||||
StringExpectation(
|
||||
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />'
|
||||
),
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
|
||||
<optional/>
|
||||
</session>
|
||||
<csi xmlns="urn:xmpp:csi:0"/>
|
||||
<sm xmlns="urn:xmpp:sm:3"/>
|
||||
</stream:features>
|
||||
''',
|
||||
),
|
||||
StringExpectation(
|
||||
"<resume xmlns='urn:xmpp:sm:3' previd='id-1' h='10' />",
|
||||
"<failed xmlns='urn:xmpp:sm:3' h='another-sequence-number'><item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></failed>",
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
|
||||
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||
ignoreId: true
|
||||
),
|
||||
StringExpectation(
|
||||
"<enable xmlns='urn:xmpp:sm:3' resume='true' />",
|
||||
'<enabled xmlns="urn:xmpp:sm:3" id="id-2" resume="true" />'
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket);
|
||||
conn.setConnectionSettings(ConnectionSettings(
|
||||
jid: JID.fromString('polynomdivision@test.server'),
|
||||
password: 'aaaa',
|
||||
useDirectTLS: true,
|
||||
allowPlainAuth: true,
|
||||
),);
|
||||
conn.registerManagers([
|
||||
PresenceManager(),
|
||||
RosterManager(),
|
||||
DiscoManager(),
|
||||
PingManager(),
|
||||
StreamManagementManager(),
|
||||
]);
|
||||
conn.registerFeatureNegotiators(
|
||||
[
|
||||
SaslPlainNegotiator(),
|
||||
ResourceBindingNegotiator(),
|
||||
StreamManagementNegotiator(),
|
||||
]
|
||||
);
|
||||
conn.getManagerById<StreamManagementManager>(smManager)!
|
||||
.setState(
|
||||
StreamManagementState(
|
||||
10,
|
||||
10,
|
||||
streamResumptionId: 'id-1',
|
||||
),
|
||||
);
|
||||
|
||||
await conn.connect();
|
||||
await Future.delayed(const Duration(seconds: 3));
|
||||
expect(fakeSocket.getState(), 7);
|
||||
expect(await conn.getConnectionState(), XmppConnectionState.connected);
|
||||
expect(
|
||||
conn
|
||||
.getManagerById<StreamManagementManager>(smManager)!
|
||||
.isStreamManagementEnabled(),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test('Test a successful stream resumption', () async {
|
||||
final fakeSocket = StubTCPSocket(
|
||||
play: [
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||
<mechanism>PLAIN</mechanism>
|
||||
</mechanisms>
|
||||
</stream:features>''',
|
||||
),
|
||||
StringExpectation(
|
||||
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />'
|
||||
),
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
|
||||
<optional/>
|
||||
</session>
|
||||
<csi xmlns="urn:xmpp:csi:0"/>
|
||||
<sm xmlns="urn:xmpp:sm:3"/>
|
||||
</stream:features>
|
||||
''',
|
||||
),
|
||||
StringExpectation(
|
||||
"<resume xmlns='urn:xmpp:sm:3' previd='id-1' h='10' />",
|
||||
"<resumed xmlns='urn:xmpp:sm:3' h='id-1' h='12' />",
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket);
|
||||
conn.setConnectionSettings(ConnectionSettings(
|
||||
jid: JID.fromString('polynomdivision@test.server'),
|
||||
password: 'aaaa',
|
||||
useDirectTLS: true,
|
||||
allowPlainAuth: true,
|
||||
),);
|
||||
conn.registerManagers([
|
||||
PresenceManager(),
|
||||
RosterManager(),
|
||||
DiscoManager(),
|
||||
PingManager(),
|
||||
StreamManagementManager(),
|
||||
]);
|
||||
conn.registerFeatureNegotiators(
|
||||
[
|
||||
SaslPlainNegotiator(),
|
||||
ResourceBindingNegotiator(),
|
||||
StreamManagementNegotiator(),
|
||||
]
|
||||
);
|
||||
conn.getManagerById<StreamManagementManager>(smManager)!
|
||||
.setState(
|
||||
StreamManagementState(
|
||||
10,
|
||||
10,
|
||||
streamResumptionId: 'id-1',
|
||||
),
|
||||
);
|
||||
|
||||
await conn.connect(lastResource: 'abc123');
|
||||
await Future.delayed(const Duration(seconds: 3), () async {
|
||||
expect(fakeSocket.getState(), 5);
|
||||
expect(await conn.getConnectionState(), XmppConnectionState.connected);
|
||||
final sm = conn.getManagerById<StreamManagementManager>(smManager)!;
|
||||
expect(sm.isStreamManagementEnabled(), true);
|
||||
expect(sm.streamResumed, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
36
packages/moxxmpp/test/xeps/xep_0280_test.dart
Normal file
36
packages/moxxmpp/test/xeps/xep_0280_test.dart
Normal file
@ -0,0 +1,36 @@
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
import '../helpers/xmpp.dart';
|
||||
|
||||
void main() {
|
||||
test("Test if we're vulnerable against CVE-2020-26547 style vulnerabilities", () async {
|
||||
final attributes = XmppManagerAttributes(
|
||||
sendStanza: (stanza, { StanzaFromType addFrom = StanzaFromType.full, bool addId = true, bool retransmitted = false, bool awaitable = true, bool encrypted = false }) async {
|
||||
// ignore: avoid_print
|
||||
print('==> ${stanza.toXml()}');
|
||||
return XMLNode(tag: 'iq', attributes: { 'type': 'result' });
|
||||
},
|
||||
sendNonza: (nonza) {},
|
||||
sendEvent: (event) {},
|
||||
getManagerById: getManagerNullStub,
|
||||
getConnectionSettings: () => ConnectionSettings(
|
||||
jid: JID.fromString('bob@xmpp.example'),
|
||||
password: 'password',
|
||||
useDirectTLS: true,
|
||||
allowPlainAuth: false,
|
||||
),
|
||||
isFeatureSupported: (_) => false,
|
||||
getFullJID: () => JID.fromString('bob@xmpp.example/uwu'),
|
||||
getSocket: () => StubTCPSocket(play: []),
|
||||
getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])),
|
||||
getNegotiatorById: getNegotiatorNullStub,
|
||||
);
|
||||
final manager = CarbonsManager();
|
||||
manager.register(attributes);
|
||||
await manager.enableCarbons();
|
||||
|
||||
expect(manager.isCarbonValid(JID.fromString('mallory@evil.example')), false);
|
||||
expect(manager.isCarbonValid(JID.fromString('bob@xmpp.example')), true);
|
||||
expect(manager.isCarbonValid(JID.fromString('bob@xmpp.example/abc')), false);
|
||||
});
|
||||
}
|
87
packages/moxxmpp/test/xeps/xep_0352_test.dart
Normal file
87
packages/moxxmpp/test/xeps/xep_0352_test.dart
Normal file
@ -0,0 +1,87 @@
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
import '../helpers/xmpp.dart';
|
||||
|
||||
class MockedCSINegotiator extends CSINegotiator {
|
||||
MockedCSINegotiator(this._isSupported);
|
||||
|
||||
final bool _isSupported;
|
||||
|
||||
@override
|
||||
bool get isSupported => _isSupported;
|
||||
}
|
||||
|
||||
T? getSupportedCSINegotiator<T extends XmppFeatureNegotiatorBase>(String id) {
|
||||
if (id == csiNegotiator) {
|
||||
return MockedCSINegotiator(true) as T;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
T? getUnsupportedCSINegotiator<T extends XmppFeatureNegotiatorBase>(String id) {
|
||||
if (id == csiNegotiator) {
|
||||
return MockedCSINegotiator(false) as T;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('Test the XEP-0352 implementation', () {
|
||||
test('Test setting the CSI state when CSI is unsupported', () {
|
||||
var nonzaSent = false;
|
||||
final csi = CSIManager();
|
||||
csi.register(XmppManagerAttributes(
|
||||
sendStanza: (_, { StanzaFromType addFrom = StanzaFromType.full, bool addId = true, bool retransmitted = false, bool awaitable = true, bool encrypted = false }) async => XMLNode(tag: 'hallo'),
|
||||
sendEvent: (event) {},
|
||||
sendNonza: (nonza) {
|
||||
nonzaSent = true;
|
||||
},
|
||||
getConnectionSettings: () => ConnectionSettings(
|
||||
jid: JID.fromString('some.user@example.server'),
|
||||
password: 'password',
|
||||
useDirectTLS: true,
|
||||
allowPlainAuth: false,
|
||||
),
|
||||
getManagerById: getManagerNullStub,
|
||||
getNegotiatorById: getUnsupportedCSINegotiator,
|
||||
isFeatureSupported: (_) => false,
|
||||
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'),
|
||||
getSocket: () => StubTCPSocket(play: []),
|
||||
getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])),
|
||||
),
|
||||
);
|
||||
|
||||
csi.setActive();
|
||||
csi.setInactive();
|
||||
|
||||
expect(nonzaSent, false, reason: 'Expected that no nonza is sent');
|
||||
});
|
||||
test('Test setting the CSI state when CSI is supported', () {
|
||||
final csi = CSIManager();
|
||||
csi.register(XmppManagerAttributes(
|
||||
sendStanza: (_, { StanzaFromType addFrom = StanzaFromType.full, bool addId = true, bool retransmitted = false, bool awaitable = true, bool encrypted = false }) async => XMLNode(tag: 'hallo'),
|
||||
sendEvent: (event) {},
|
||||
sendNonza: (nonza) {
|
||||
expect(nonza.attributes['xmlns'] == csiXmlns, true, reason: "Expected only nonzas with XMLNS '$csiXmlns'");
|
||||
},
|
||||
getConnectionSettings: () => ConnectionSettings(
|
||||
jid: JID.fromString('some.user@example.server'),
|
||||
password: 'password',
|
||||
useDirectTLS: true,
|
||||
allowPlainAuth: false,
|
||||
),
|
||||
getManagerById: getManagerNullStub,
|
||||
getNegotiatorById: getSupportedCSINegotiator,
|
||||
isFeatureSupported: (_) => false,
|
||||
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'),
|
||||
getSocket: () => StubTCPSocket(play: []),
|
||||
getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])),
|
||||
),);
|
||||
|
||||
csi.setActive();
|
||||
csi.setInactive();
|
||||
});
|
||||
});
|
||||
}
|
54
packages/moxxmpp/test/xeps/xep_0363_test.dart
Normal file
54
packages/moxxmpp/test/xeps/xep_0363_test.dart
Normal file
@ -0,0 +1,54 @@
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('Test the XEP-0363 header preparation', () {
|
||||
test('invariance', () {
|
||||
final headers = {
|
||||
'authorization': 'Basic Base64String==',
|
||||
'cookie': 'foo=bar; user=romeo'
|
||||
};
|
||||
expect(
|
||||
prepareHeaders(headers),
|
||||
headers,
|
||||
);
|
||||
});
|
||||
test('invariance through uppercase', () {
|
||||
final headers = {
|
||||
'Authorization': 'Basic Base64String==',
|
||||
'Cookie': 'foo=bar; user=romeo'
|
||||
};
|
||||
expect(
|
||||
prepareHeaders(headers),
|
||||
headers,
|
||||
);
|
||||
});
|
||||
test('remove unspecified headers', () {
|
||||
final headers = {
|
||||
'Authorization': 'Basic Base64String==',
|
||||
'Cookie': 'foo=bar; user=romeo',
|
||||
'X-Tracking': 'Base64String=='
|
||||
};
|
||||
expect(
|
||||
prepareHeaders(headers),
|
||||
{
|
||||
'Authorization': 'Basic Base64String==',
|
||||
'Cookie': 'foo=bar; user=romeo',
|
||||
}
|
||||
);
|
||||
});
|
||||
test('remove newlines', () {
|
||||
final headers = {
|
||||
'Authorization': '\n\nBasic Base64String==\n\n',
|
||||
'\nCookie\r\n': 'foo=bar; user=romeo',
|
||||
};
|
||||
expect(
|
||||
prepareHeaders(headers),
|
||||
{
|
||||
'Authorization': 'Basic Base64String==',
|
||||
'Cookie': 'foo=bar; user=romeo',
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
33
packages/moxxmpp/test/xeps/xep_0447.dart
Normal file
33
packages/moxxmpp/test/xeps/xep_0447.dart
Normal file
@ -0,0 +1,33 @@
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('Test correct SFS parsing', () {
|
||||
final sfs = StatelessFileSharingData.fromXML(
|
||||
// Taken from https://xmpp.org/extensions/xep-0447.html#file-sharing
|
||||
XMLNode.fromString('''
|
||||
<file-sharing xmlns='urn:xmpp:sfs:0' disposition='inline'>
|
||||
<file xmlns='urn:xmpp:file:metadata:0'>
|
||||
<media-type>image/jpeg</media-type>
|
||||
<name>summit.jpg</name>
|
||||
<size>3032449</size>
|
||||
<dimensions>4096x2160</dimensions>
|
||||
<hash xmlns='urn:xmpp:hashes:2' algo='sha3-256'>2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=</hash>
|
||||
<hash xmlns='urn:xmpp:hashes:2' algo='id-blake2b256'>2AfMGH8O7UNPTvUVAM9aK13mpCY=</hash>
|
||||
<desc>Photo from the summit.</desc>
|
||||
<thumbnail xmlns='urn:xmpp:thumbs:1' uri='cid:sha1+ffd7c8d28e9c5e82afea41f97108c6b4@bob.xmpp.org' media-type='image/png' width='128' height='96'/>
|
||||
</file>
|
||||
<sources>
|
||||
<url-data xmlns='http://jabber.org/protocol/url-data' target='https://download.montague.lit/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/summit.jpg' />
|
||||
<jinglepub xmlns='urn:xmpp:jinglepub:1' from='romeo@montague.lit/resource' id='9559976B-3FBF-4E7E-B457-2DAA225972BB'>
|
||||
<description xmlns='urn:xmpp:jingle:apps:file-transfer:5' />
|
||||
</jinglepub>
|
||||
</sources>
|
||||
</file-sharing>
|
||||
'''),
|
||||
);
|
||||
|
||||
expect(sfs.metadata.hashes['sha3-256'], '2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=');
|
||||
expect(sfs.metadata.hashes['id-blake2b256'], '2AfMGH8O7UNPTvUVAM9aK13mpCY=');
|
||||
});
|
||||
}
|
82
packages/moxxmpp/test/xmlstreambuffer_test.dart
Normal file
82
packages/moxxmpp/test/xmlstreambuffer_test.dart
Normal file
@ -0,0 +1,82 @@
|
||||
import 'dart:async';
|
||||
import 'package:moxxmpp/src/buffer.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('Test non-broken up Xml data', () async {
|
||||
var childa = false;
|
||||
var childb = false;
|
||||
|
||||
final buffer = XmlStreamBuffer();
|
||||
final controller = StreamController<String>();
|
||||
|
||||
controller
|
||||
.stream
|
||||
.transform(buffer)
|
||||
.forEach((node) {
|
||||
if (node.tag == 'childa') {
|
||||
childa = true;
|
||||
} else if (node.tag == 'childb') {
|
||||
childb = true;
|
||||
}
|
||||
});
|
||||
controller.add('<childa /><childb />');
|
||||
|
||||
await Future.delayed(const Duration(seconds: 2), () {
|
||||
expect(childa, true);
|
||||
expect(childb, true);
|
||||
});
|
||||
});
|
||||
test('Test broken up Xml data', () async {
|
||||
var childa = false;
|
||||
var childb = false;
|
||||
|
||||
final buffer = XmlStreamBuffer();
|
||||
final controller = StreamController<String>();
|
||||
|
||||
controller
|
||||
.stream
|
||||
.transform(buffer)
|
||||
.forEach((node) {
|
||||
if (node.tag == 'childa') {
|
||||
childa = true;
|
||||
} else if (node.tag == 'childb') {
|
||||
childb = true;
|
||||
}
|
||||
});
|
||||
controller.add('<childa');
|
||||
controller.add(' /><childb />');
|
||||
|
||||
await Future.delayed(const Duration(seconds: 2), () {
|
||||
expect(childa, true);
|
||||
expect(childb, true);
|
||||
});
|
||||
});
|
||||
|
||||
test('Test closing the stream', () async {
|
||||
var childa = false;
|
||||
var childb = false;
|
||||
|
||||
final buffer = XmlStreamBuffer();
|
||||
final controller = StreamController<String>();
|
||||
|
||||
controller
|
||||
.stream
|
||||
.transform(buffer)
|
||||
.forEach((node) {
|
||||
if (node.tag == 'childa') {
|
||||
childa = true;
|
||||
} else if (node.tag == 'childb') {
|
||||
childb = true;
|
||||
}
|
||||
});
|
||||
controller.add('<childa');
|
||||
controller.add(' /><childb />');
|
||||
controller.add('</stream:stream>');
|
||||
|
||||
await Future.delayed(const Duration(seconds: 2), () {
|
||||
expect(childa, true);
|
||||
expect(childb, true);
|
||||
});
|
||||
});
|
||||
}
|
354
packages/moxxmpp/test/xmpp_test.dart
Normal file
354
packages/moxxmpp/test/xmpp_test.dart
Normal file
@ -0,0 +1,354 @@
|
||||
import 'dart:async';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'helpers/logging.dart';
|
||||
import 'helpers/xmpp.dart';
|
||||
|
||||
/// Returns true if the roster manager triggeres an event for a given stanza
|
||||
Future<bool> testRosterManager(String bareJid, String resource, String stanzaString) async {
|
||||
var eventTriggered = false;
|
||||
final roster = RosterManager();
|
||||
roster.register(XmppManagerAttributes(
|
||||
sendStanza: (_, { StanzaFromType addFrom = StanzaFromType.full, bool addId = true, bool retransmitted = false, bool awaitable = true, bool encrypted = false }) async => XMLNode(tag: 'hallo'),
|
||||
sendEvent: (event) {
|
||||
eventTriggered = true;
|
||||
},
|
||||
sendNonza: (_) {},
|
||||
getConnectionSettings: () => ConnectionSettings(
|
||||
jid: JID.fromString(bareJid),
|
||||
password: 'password',
|
||||
useDirectTLS: true,
|
||||
allowPlainAuth: false,
|
||||
),
|
||||
getManagerById: getManagerNullStub,
|
||||
getNegotiatorById: getNegotiatorNullStub,
|
||||
isFeatureSupported: (_) => false,
|
||||
getFullJID: () => JID.fromString('$bareJid/$resource'),
|
||||
getSocket: () => StubTCPSocket(play: []),
|
||||
getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])),
|
||||
),);
|
||||
|
||||
final stanza = Stanza.fromXMLNode(XMLNode.fromString(stanzaString));
|
||||
for (final handler in roster.getIncomingStanzaHandlers()) {
|
||||
if (handler.matches(stanza)) await handler.callback(stanza, StanzaHandlerData(false, false, null, stanza));
|
||||
}
|
||||
|
||||
return eventTriggered;
|
||||
}
|
||||
|
||||
void main() {
|
||||
initLogger();
|
||||
|
||||
test('Test a successful login attempt with no SM', () async {
|
||||
final fakeSocket = StubTCPSocket(
|
||||
play: [
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||
<mechanism>PLAIN</mechanism>
|
||||
</mechanisms>
|
||||
</stream:features>''',
|
||||
),
|
||||
StringExpectation(
|
||||
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />'
|
||||
),
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
|
||||
<optional/>
|
||||
</session>
|
||||
<csi xmlns="urn:xmpp:csi:0"/>
|
||||
<sm xmlns="urn:xmpp:sm:3"/>
|
||||
</stream:features>
|
||||
''',
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
|
||||
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||
ignoreId: true,
|
||||
),
|
||||
/*
|
||||
Expectation(
|
||||
XMLNode.xmlns(
|
||||
tag: 'presence',
|
||||
xmlns: 'jabber:client',
|
||||
attributes: { 'from': 'polynomdivision@test.server/MU29eEZn' },
|
||||
children: [
|
||||
XMLNode(
|
||||
tag: 'show',
|
||||
text: 'chat',
|
||||
),
|
||||
XMLNode.xmlns(
|
||||
tag: 'c',
|
||||
xmlns: 'http://jabber.org/protocol/caps',
|
||||
attributes: {
|
||||
// TODO: Somehow make the test ignore this attribute
|
||||
'ver': 'QRTBC5cg/oYd+UOTYazSQR4zb/I=',
|
||||
'node': 'http://moxxy.im',
|
||||
'hash': 'sha-1'
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
XMLNode(
|
||||
tag: 'presence',
|
||||
),
|
||||
),
|
||||
*/
|
||||
],
|
||||
);
|
||||
// TODO: This test is broken since we query the server and enable carbons
|
||||
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket);
|
||||
conn.setConnectionSettings(ConnectionSettings(
|
||||
jid: JID.fromString('polynomdivision@test.server'),
|
||||
password: 'aaaa',
|
||||
useDirectTLS: true,
|
||||
allowPlainAuth: true,
|
||||
),);
|
||||
conn.registerManagers([
|
||||
PresenceManager(),
|
||||
RosterManager(),
|
||||
DiscoManager(),
|
||||
PingManager(),
|
||||
StreamManagementManager(),
|
||||
]);
|
||||
conn.registerFeatureNegotiators(
|
||||
[
|
||||
SaslPlainNegotiator(),
|
||||
SaslScramNegotiator(10, '', '', ScramHashType.sha512),
|
||||
ResourceBindingNegotiator(),
|
||||
StreamManagementNegotiator(),
|
||||
]
|
||||
);
|
||||
|
||||
await conn.connect();
|
||||
await Future.delayed(const Duration(seconds: 3), () {
|
||||
expect(fakeSocket.getState(), /*6*/ 5);
|
||||
});
|
||||
});
|
||||
|
||||
test('Test a failed SASL auth', () async {
|
||||
final fakeSocket = StubTCPSocket(
|
||||
play: [
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||
<mechanism>PLAIN</mechanism>
|
||||
</mechanisms>
|
||||
</stream:features>''',
|
||||
),
|
||||
StringExpectation(
|
||||
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||
'<failure xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><not-authorized /></failure>'
|
||||
),
|
||||
],
|
||||
);
|
||||
var receivedEvent = false;
|
||||
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket);
|
||||
conn.setConnectionSettings(ConnectionSettings(
|
||||
jid: JID.fromString('polynomdivision@test.server'),
|
||||
password: 'aaaa',
|
||||
useDirectTLS: true,
|
||||
allowPlainAuth: true,
|
||||
),);
|
||||
conn.registerManagers([
|
||||
PresenceManager(),
|
||||
RosterManager(),
|
||||
DiscoManager(),
|
||||
PingManager(),
|
||||
]);
|
||||
conn.registerFeatureNegotiators([
|
||||
SaslPlainNegotiator()
|
||||
]);
|
||||
|
||||
conn.asBroadcastStream().listen((event) {
|
||||
if (event is AuthenticationFailedEvent && event.saslError == 'not-authorized') {
|
||||
receivedEvent = true;
|
||||
}
|
||||
});
|
||||
|
||||
await conn.connect();
|
||||
await Future.delayed(const Duration(seconds: 3), () {
|
||||
expect(receivedEvent, true);
|
||||
});
|
||||
});
|
||||
|
||||
test('Test another failed SASL auth', () async {
|
||||
final fakeSocket = StubTCPSocket(
|
||||
play: [
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||
<mechanism>PLAIN</mechanism>
|
||||
</mechanisms>
|
||||
</stream:features>''',
|
||||
),
|
||||
StringExpectation(
|
||||
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||
'<failure xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism-too-weak /></failure>',
|
||||
),
|
||||
],
|
||||
);
|
||||
var receivedEvent = false;
|
||||
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket);
|
||||
conn.setConnectionSettings(ConnectionSettings(
|
||||
jid: JID.fromString('polynomdivision@test.server'),
|
||||
password: 'aaaa',
|
||||
useDirectTLS: true,
|
||||
allowPlainAuth: true,
|
||||
),);
|
||||
conn.registerManagers([
|
||||
PresenceManager(),
|
||||
RosterManager(),
|
||||
DiscoManager(),
|
||||
PingManager(),
|
||||
]);
|
||||
conn.registerFeatureNegotiators([
|
||||
SaslPlainNegotiator()
|
||||
]);
|
||||
|
||||
conn.asBroadcastStream().listen((event) {
|
||||
if (event is AuthenticationFailedEvent && event.saslError == 'mechanism-too-weak') {
|
||||
receivedEvent = true;
|
||||
}
|
||||
});
|
||||
|
||||
await conn.connect();
|
||||
await Future.delayed(const Duration(seconds: 3), () {
|
||||
expect(receivedEvent, true);
|
||||
});
|
||||
});
|
||||
|
||||
/*test('Test choosing SCRAM-SHA-1', () async {
|
||||
final fakeSocket = StubTCPSocket(
|
||||
play: [
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||
<mechanism>PLAIN</mechanism>
|
||||
<mechanism>SCRAM-SHA-1</mechanism>
|
||||
</mechanisms>
|
||||
</stream:features>''',
|
||||
),
|
||||
// TODO(Unknown): This test is currently broken
|
||||
StringExpectation(
|
||||
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='SCRAM-SHA-1'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||
"..."
|
||||
)
|
||||
],
|
||||
);
|
||||
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket);
|
||||
conn.setConnectionSettings(ConnectionSettings(
|
||||
jid: JID.fromString('polynomdivision@test.server'),
|
||||
password: 'aaaa',
|
||||
useDirectTLS: true,
|
||||
allowPlainAuth: false,
|
||||
),);
|
||||
conn.registerManagers([
|
||||
PresenceManager(),
|
||||
RosterManager(),
|
||||
DiscoManager(),
|
||||
PingManager(),
|
||||
]);
|
||||
conn.registerFeatureNegotiators([
|
||||
SaslPlainNegotiator(),
|
||||
SaslScramNegotiator(10, '', '', ScramHashType.sha1),
|
||||
]);
|
||||
|
||||
await conn.connect();
|
||||
await Future.delayed(const Duration(seconds: 3), () {
|
||||
expect(fakeSocket.getState(), 2);
|
||||
});
|
||||
});*/
|
||||
|
||||
group('Test roster pushes', () {
|
||||
test('Test for a CVE-2015-8688 style vulnerability', () async {
|
||||
var eventTriggered = false;
|
||||
final roster = RosterManager();
|
||||
roster.register(XmppManagerAttributes(
|
||||
sendStanza: (_, { StanzaFromType addFrom = StanzaFromType.full, bool addId = true, bool retransmitted = false, bool awaitable = true, bool encrypted = false }) async => XMLNode(tag: 'hallo'),
|
||||
sendEvent: (event) {
|
||||
eventTriggered = true;
|
||||
},
|
||||
sendNonza: (_) {},
|
||||
getConnectionSettings: () => ConnectionSettings(
|
||||
jid: JID.fromString('some.user@example.server'),
|
||||
password: 'password',
|
||||
useDirectTLS: true,
|
||||
allowPlainAuth: false,
|
||||
),
|
||||
getManagerById: getManagerNullStub,
|
||||
getNegotiatorById: getNegotiatorNullStub,
|
||||
isFeatureSupported: (_) => false,
|
||||
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'),
|
||||
getSocket: () => StubTCPSocket(play: []),
|
||||
getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])),
|
||||
),);
|
||||
|
||||
// NOTE: Based on https://gultsch.de/gajim_roster_push_and_message_interception.html
|
||||
// NOTE: Added a from attribute as a server would add it itself.
|
||||
final maliciousStanza = Stanza.fromXMLNode(XMLNode.fromString("<iq type=\"set\" from=\"eve@siacs.eu/bbbbb\" to=\"some.user@example.server/aaaaa\"><query xmlns='jabber:iq:roster'><item subscription=\"both\" jid=\"eve@siacs.eu\" name=\"Bob\" /></query></iq>"));
|
||||
|
||||
for (final handler in roster.getIncomingStanzaHandlers()) {
|
||||
if (handler.matches(maliciousStanza)) await handler.callback(maliciousStanza, StanzaHandlerData(false, false, null, maliciousStanza));
|
||||
}
|
||||
|
||||
expect(eventTriggered, false, reason: 'Was able to inject a malicious roster push');
|
||||
});
|
||||
test('The manager should accept pushes from our bare jid', () async {
|
||||
final result = await testRosterManager('test.user@server.example', 'aaaaa', "<iq from='test.user@server.example' type='result' id='82c2aa1e-cac3-4f62-9e1f-bbe6b057daf3' to='test.user@server.example/aaaaa' xmlns='jabber:client'><query ver='64' xmlns='jabber:iq:roster'><item jid='some.other.user@server.example' subscription='to' /></query></iq>");
|
||||
expect(result, true, reason: 'Roster pushes from our bare JID should be accepted');
|
||||
});
|
||||
test('The manager should accept pushes from a jid that, if the resource is stripped, is our bare jid', () async {
|
||||
final result1 = await testRosterManager('test.user@server.example', 'aaaaa', "<iq from='test.user@server.example/aaaaa' type='result' id='82c2aa1e-cac3-4f62-9e1f-bbe6b057daf3' to='test.user@server.example/aaaaa' xmlns='jabber:client'><query ver='64' xmlns='jabber:iq:roster'><item jid='some.other.user@server.example' subscription='to' /></query></iq>");
|
||||
expect(result1, true, reason: 'Roster pushes should be accepted if the bare JIDs are the same');
|
||||
|
||||
final result2 = await testRosterManager('test.user@server.example', 'aaaaa', "<iq from='test.user@server.example/bbbbb' type='result' id='82c2aa1e-cac3-4f62-9e1f-bbe6b057daf3' to='test.user@server.example/aaaaa' xmlns='jabber:client'><query ver='64' xmlns='jabber:iq:roster'><item jid='some.other.user@server.example' subscription='to' /></query></iq>");
|
||||
expect(result2, true, reason: 'Roster pushes should be accepted if the bare JIDs are the same');
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user