summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWill Thompson <will.thompson@collabora.co.uk>2012-12-06 18:35:37 +0000
committerWill Thompson <will.thompson@collabora.co.uk>2012-12-06 18:35:37 +0000
commit51e301d6366e7e5f0f203f19fd594cef27ae05db (patch)
treed83fa853c1939ba5f1aee395bb47c205f30b0379
parentc33a817b5bcf9f6bec7c744e4c5a3f0b96df4118 (diff)
parent52beb82932c6c31f43e6b4465a22f5543cff1d06 (diff)
Merge branch 'xep-0184'
https://bugs.freedesktop.org/show_bug.cgi?id=47378
-rw-r--r--src/capabilities.c2
-rw-r--r--src/im-channel.c127
-rw-r--r--src/im-channel.h5
-rw-r--r--src/im-factory.c50
-rw-r--r--src/namespaces.h1
-rw-r--r--src/roster.c3
-rw-r--r--tests/twisted/Makefile.am2
-rw-r--r--tests/twisted/ns.py1
-rw-r--r--tests/twisted/presence/__init__.py0
-rw-r--r--tests/twisted/text/receipts.py197
10 files changed, 365 insertions, 23 deletions
diff --git a/src/capabilities.c b/src/capabilities.c
index 9d8a97f69..1c4801fd8 100644
--- a/src/capabilities.c
+++ b/src/capabilities.c
@@ -61,6 +61,8 @@ static const Feature self_advertised_features[] =
{ FEATURE_FIXED, NS_TUBES },
{ FEATURE_FIXED, NS_BYTESTREAMS },
{ FEATURE_FIXED, NS_VERSION },
+ { FEATURE_FIXED, NS_LAST },
+ { FEATURE_FIXED, NS_RECEIPTS },
#ifdef ENABLE_FILE_TRANSFER
{ FEATURE_OPTIONAL, NS_FILE_TRANSFER },
diff --git a/src/im-channel.c b/src/im-channel.c
index 1d1cf2fa5..9cbdbfaab 100644
--- a/src/im-channel.c
+++ b/src/im-channel.c
@@ -81,6 +81,7 @@ typedef struct {
GabbleIMChannel *channel;
TpMessage *message;
gchar *token;
+ TpMessageSendingFlags flags;
} _GabbleIMSendMessageCtx;
static GPtrArray *
@@ -144,6 +145,10 @@ gabble_im_channel_constructed (GObject *obj)
tp_message_mixin_init (obj, G_STRUCT_OFFSET (GabbleIMChannel, message_mixin),
base_conn);
+ /* We deliberately do not include
+ * TP_DELIVERY_REPORTING_SUPPORT_FLAG_RECEIVE_SUCCESSES here, even though we
+ * support requesting receipts, because XEP-0184 provides no guarantees.
+ */
tp_message_mixin_implement_sending (obj, _gabble_im_channel_send_message,
G_N_ELEMENTS (types), types, 0,
TP_DELIVERY_REPORTING_SUPPORT_FLAG_RECEIVE_FAILURES,
@@ -237,6 +242,28 @@ chat_states_supported (GabbleIMChannel *self,
}
}
+static gboolean
+receipts_conceivably_supported (
+ GabbleIMChannel *self)
+{
+ TpBaseChannel *base = (TpBaseChannel *) self;
+ GabbleConnection *conn =
+ GABBLE_CONNECTION (tp_base_channel_get_connection (base));
+ GabblePresence *presence;
+
+ presence = gabble_presence_cache_get (conn->presence_cache,
+ tp_base_channel_get_target_handle (base));
+
+ /* ...except it's never null because _parse_message_message() in
+ * presence-cache.c. I hate this exactly as much as I did when I wrote the
+ * FIXME on that function. */
+ if (presence != NULL)
+ return gabble_presence_has_cap (presence, NS_RECEIPTS);
+
+ /* Otherwise ... who knows. Why not ask for one? */
+ return TRUE;
+}
+
static void
gabble_im_channel_dispose (GObject *object)
{
@@ -300,7 +327,7 @@ _gabble_im_channel_message_sent_cb (GObject *source,
if (wocky_porter_send_finish (porter, res, &error))
{
- tp_message_mixin_sent ((GObject *) chan, message, 0,
+ tp_message_mixin_sent ((GObject *) chan, message, context->flags,
context->token, NULL);
}
else
@@ -345,21 +372,30 @@ _gabble_im_channel_send_message (GObject *object,
tp_base_connection_get_self_handle (base_conn), state);
}
- /* We don't support providing successful delivery reports. */
- flags = 0;
-
stanza = gabble_message_util_build_stanza (message,
gabble_conn, 0, state, priv->peer_jid,
priv->send_nick, &id, &error);
-
if (stanza != NULL)
{
+ if ((flags & TP_MESSAGE_SENDING_FLAG_REPORT_DELIVERY) &&
+ receipts_conceivably_supported (self))
+ {
+ wocky_node_add_child_ns (wocky_stanza_get_top_node (stanza),
+ "request", NS_RECEIPTS);
+ flags = TP_MESSAGE_SENDING_FLAG_REPORT_DELIVERY;
+ }
+ else
+ {
+ flags = 0;
+ }
+
porter = gabble_connection_dup_porter (gabble_conn);
context = g_slice_new0 (_GabbleIMSendMessageCtx);
context->channel = g_object_ref (base);
context->message = g_object_ref (message);
context->token = id;
+ context->flags = flags;
wocky_porter_send_async (porter, stanza, NULL,
_gabble_im_channel_message_sent_cb, context);
g_object_unref (porter);
@@ -367,7 +403,7 @@ _gabble_im_channel_send_message (GObject *object,
}
else
{
- tp_message_mixin_sent (object, message, flags, NULL, error);
+ tp_message_mixin_sent (object, message, 0, NULL, error);
g_error_free (error);
}
@@ -400,29 +436,61 @@ build_message (
return msg;
}
+static void
+maybe_send_delivery_report (
+ GabbleIMChannel *self,
+ WockyStanza *message,
+ const gchar *jid,
+ const gchar *id)
+{
+ TpBaseChannel *base = TP_BASE_CHANNEL (self);
+ TpHandle target = tp_base_channel_get_target_handle (base);
+ TpBaseConnection *base_conn = tp_base_channel_get_connection (base);
+ GabbleConnection *conn = GABBLE_CONNECTION (base_conn);
+ WockyStanza *report;
+
+ if (id == NULL)
+ return;
+
+ if (wocky_node_get_child_ns (wocky_stanza_get_top_node (message),
+ "request", NS_RECEIPTS) == NULL)
+ return;
+
+ if (conn->self_presence->status == GABBLE_PRESENCE_HIDDEN ||
+ !gabble_roster_handle_gets_presence_from_us (conn->roster, target))
+ return;
+
+ report = wocky_stanza_build (
+ WOCKY_STANZA_TYPE_MESSAGE, WOCKY_STANZA_SUB_TYPE_NONE,
+ NULL, jid,
+ '(', "received", ':', NS_RECEIPTS,
+ '@', "id", id,
+ ')', NULL);
+
+ _gabble_connection_send (conn, report, NULL);
+ g_object_unref (report);
+}
+
/*
* _gabble_im_channel_receive:
* @chan: a channel
+ * @message: the <message> stanza, from which all the following arguments were
+ * extracted.
* @type: the message type
* @from: the full JID we received the message from
* @timestamp: the time at which the message was sent (not the time it was
* received)
* @id: the id='' attribute from the <message/> stanza, if any
* @text: the plaintext body of the message
- * @send_error: the reason why sending @text to @sender failed, or
- * GABBLE_TEXT_CHANNEL_SEND_NO_ERROR if this call is not to report
- * a failure to send.
- * @delivery_status: if @send_error is GABBLE_TEXT_CHANNEL_SEND_NO_ERROR,
- * ignored; else the delivery status to attach to the report.
* @state: a #TpChannelChatState, or -1 if there was no chat state in the
* message.
*
* Shoves an incoming message into @chan, possibly updating the chat state at
- * the same time; or maybe this is a delivery report? Who knows! It's a magical
- * adventure.
+ * the same time.
*/
void
_gabble_im_channel_receive (GabbleIMChannel *chan,
+ WockyStanza *message,
TpChannelTextMessageType type,
const char *from,
time_t timestamp,
@@ -460,6 +528,7 @@ _gabble_im_channel_receive (GabbleIMChannel *chan,
tp_message_set_string (msg, 0, "message-token", id);
tp_message_mixin_take_received (G_OBJECT (chan), msg);
+ maybe_send_delivery_report (chan, message, from, id);
}
void
@@ -476,7 +545,7 @@ _gabble_im_channel_report_delivery (
TpBaseChannel *base_chan = (TpBaseChannel *) self;
TpBaseConnection *base_conn;
TpHandle peer;
- TpMessage *msg, *delivery_report;
+ TpMessage *delivery_report;
gchar *tmp;
g_return_if_fail (GABBLE_IS_IM_CHANNEL (self));
@@ -495,7 +564,6 @@ _gabble_im_channel_report_delivery (
priv->chat_states_supported = CHAT_STATES_UNKNOWN;
}
- msg = build_message (self, type, timestamp, text);
delivery_report = tp_cm_message_new (base_conn, 1);
tp_message_set_uint32 (delivery_report, 0, "message-type",
TP_CHANNEL_TEXT_MESSAGE_TYPE_DELIVERY_REPORT);
@@ -514,15 +582,20 @@ _gabble_im_channel_report_delivery (
if (id != NULL)
tp_message_set_string (delivery_report, 0, "delivery-token", id);
- /* This is a delivery report, so the original sender of the echoed message
- * must be us! */
- tp_cm_message_set_sender (msg, tp_base_connection_get_self_handle (base_conn));
+ if (text != NULL)
+ {
+ TpMessage *msg = build_message (self, type, timestamp, text);
+ /* This is a delivery report, so the original sender of the echoed message
+ * must be us! */
+ tp_cm_message_set_sender (msg, tp_base_connection_get_self_handle (base_conn));
- /* Since this is a delivery report, we can trust the id on the message. */
- if (id != NULL)
- tp_message_set_string (msg, 0, "message-token", id);
+ /* Since this is a delivery report, we can trust the id on the message. */
+ if (id != NULL)
+ tp_message_set_string (msg, 0, "message-token", id);
+
+ tp_cm_message_take_message (delivery_report, 0, "delivery-echo", msg);
+ }
- tp_cm_message_take_message (delivery_report, 0, "delivery-echo", msg);
tp_message_mixin_take_received (G_OBJECT (self), delivery_report);
}
@@ -550,6 +623,16 @@ _gabble_im_channel_state_receive (GabbleIMChannel *chan,
tp_base_channel_get_target_handle (base_chan), state);
}
+void
+gabble_im_channel_receive_receipt (
+ GabbleIMChannel *self,
+ const gchar *receipt_id)
+{
+ _gabble_im_channel_report_delivery (self,
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL, 0, receipt_id, NULL,
+ GABBLE_TEXT_CHANNEL_SEND_NO_ERROR, TP_DELIVERY_STATUS_DELIVERED);
+}
+
static void
gabble_im_channel_close (TpBaseChannel *base_chan)
{
diff --git a/src/im-channel.h b/src/im-channel.h
index 076565ef5..c09408484 100644
--- a/src/im-channel.h
+++ b/src/im-channel.h
@@ -25,6 +25,7 @@
#include <time.h>
#include <telepathy-glib/telepathy-glib.h>
+#include <wocky/wocky.h>
G_BEGIN_DECLS
@@ -63,6 +64,7 @@ GType gabble_im_channel_get_type (void);
GabbleIMChannelClass))
void _gabble_im_channel_receive (GabbleIMChannel *chan,
+ WockyStanza *message,
TpChannelTextMessageType type,
const char *from,
time_t timestamp,
@@ -71,6 +73,9 @@ void _gabble_im_channel_receive (GabbleIMChannel *chan,
gint state);
void _gabble_im_channel_state_receive (GabbleIMChannel *chan,
TpChannelChatState state);
+void gabble_im_channel_receive_receipt (
+ GabbleIMChannel *self,
+ const gchar *receipt_id);
void _gabble_im_channel_report_delivery (
GabbleIMChannel *self,
diff --git a/src/im-factory.c b/src/im-factory.c
index 661f6fbc1..4170d1012 100644
--- a/src/im-factory.c
+++ b/src/im-factory.c
@@ -60,6 +60,7 @@ struct _GabbleImFactoryPrivate
{
GabbleConnection *conn;
guint message_cb_id;
+ guint delivery_report_cb_id;
GHashTable *channels;
gulong status_changed_id;
@@ -256,7 +257,8 @@ im_factory_message_cb (
}
else if (body != NULL)
{
- _gabble_im_channel_receive (chan, msgtype, from, stamp, id, body, state);
+ _gabble_im_channel_receive (chan, message, msgtype, from, stamp, id,
+ body, state);
}
else if (state != -1)
{
@@ -266,6 +268,42 @@ im_factory_message_cb (
return TRUE;
}
+/* Signals incoming delivery receipts. http://xmpp.org/extensions/xep-0184.html
+ */
+static gboolean
+im_factory_receipt_cb (
+ WockyPorter *porter,
+ WockyStanza *message,
+ gpointer user_data)
+{
+ GabbleImFactory *self = GABBLE_IM_FACTORY (user_data);
+ WockyNode *received;
+ const gchar *from, *received_id;
+ GabbleIMChannel *channel;
+
+ received = wocky_node_get_child_ns (wocky_stanza_get_top_node (message),
+ "received", NS_RECEIPTS);
+ g_return_val_if_fail (received != NULL, FALSE);
+
+ received_id = wocky_node_get_attribute (received, "id");
+ if (received_id == NULL)
+ {
+ STANZA_DEBUG (message, "but *what* did you receive?!");
+ return TRUE;
+ }
+
+ from = wocky_stanza_get_from (message);
+ channel = get_channel_for_incoming_message (self, from, FALSE);
+ if (channel == NULL)
+ {
+ DEBUG ("no existing channel with '%s'; ignoring receipt", from);
+ return TRUE;
+ }
+
+ gabble_im_channel_receive_receipt (channel, received_id);
+ return TRUE;
+}
+
/**
* im_channel_closed_cb:
*
@@ -433,6 +471,10 @@ gabble_im_factory_close_all (GabbleImFactory *self)
wocky_porter_unregister_handler (porter, self->priv->message_cb_id);
self->priv->message_cb_id = 0;
+
+ wocky_porter_unregister_handler (porter, self->priv->delivery_report_cb_id);
+ self->priv->delivery_report_cb_id = 0;
+
g_object_unref (porter);
}
}
@@ -519,6 +561,12 @@ porter_available_cb (
WOCKY_STANZA_TYPE_MESSAGE, WOCKY_STANZA_SUB_TYPE_NONE,
WOCKY_PORTER_HANDLER_PRIORITY_MIN, im_factory_message_cb, self,
NULL);
+ self->priv->delivery_report_cb_id = wocky_porter_register_handler_from_anyone (porter,
+ WOCKY_STANZA_TYPE_MESSAGE, WOCKY_STANZA_SUB_TYPE_NONE,
+ WOCKY_PORTER_HANDLER_PRIORITY_MIN, im_factory_receipt_cb, self,
+ '(',
+ "received", ':', NS_RECEIPTS,
+ ')', NULL);
g_object_get (conn, "stream-server", &stream_server, NULL);
diff --git a/src/namespaces.h b/src/namespaces.h
index 0d06d7d54..2a2d8f91b 100644
--- a/src/namespaces.h
+++ b/src/namespaces.h
@@ -103,6 +103,7 @@
#define NS_PRESENCE_INVISIBLE "presence-invisible"
#define NS_PRIVACY "jabber:iq:privacy"
#define NS_INVISIBLE "urn:xmpp:invisible:0"
+#define NS_RECEIPTS "urn:xmpp:receipts"
#define NS_REGISTER "jabber:iq:register"
#define NS_ROSTER "jabber:iq:roster"
#define NS_SEARCH "jabber:iq:search"
diff --git a/src/roster.c b/src/roster.c
index 64360cea0..d2cc4dae8 100644
--- a/src/roster.c
+++ b/src/roster.c
@@ -3601,6 +3601,9 @@ gabble_roster_handle_gets_presence_from_us (GabbleRoster *self,
if (item == NULL)
return FALSE;
+ if (item->blocked)
+ return FALSE;
+
return (item->subscription == GABBLE_ROSTER_SUBSCRIPTION_FROM ||
item->subscription == GABBLE_ROSTER_SUBSCRIPTION_BOTH);
}
diff --git a/tests/twisted/Makefile.am b/tests/twisted/Makefile.am
index 8e88892d5..7797d5173 100644
--- a/tests/twisted/Makefile.am
+++ b/tests/twisted/Makefile.am
@@ -113,6 +113,7 @@ TWISTED_TESTS = \
text/facebook-own-message.py \
text/initiate.py \
text/initiate-requestotron.py \
+ text/receipts.py \
text/respawn.py \
text/send-error.py \
text/send-to-correct-resource.py \
@@ -268,6 +269,7 @@ TWISTED_OTHER_FILES = \
mucutil.py \
ns.py \
olpc/util.py \
+ presence/__init__.py \
presence/invisible_helper.py \
rostertest.py \
sasl/saslutil.py \
diff --git a/tests/twisted/ns.py b/tests/twisted/ns.py
index 48b656b30..30f9a8114 100644
--- a/tests/twisted/ns.py
+++ b/tests/twisted/ns.py
@@ -57,6 +57,7 @@ OLPC_CURRENT_ACTIVITY = "http://laptop.org/xmpp/current-activity"
OLPC_CURRENT_ACTIVITY_NOTIFY = "%s+notify" % OLPC_CURRENT_ACTIVITY
PUBSUB = "http://jabber.org/protocol/pubsub"
PUBSUB_EVENT = "%s#event" % PUBSUB
+RECEIPTS = "urn:xmpp:receipts"
REGISTER = "jabber:iq:register"
ROSTER = "jabber:iq:roster"
SEARCH = 'jabber:iq:search'
diff --git a/tests/twisted/presence/__init__.py b/tests/twisted/presence/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/twisted/presence/__init__.py
diff --git a/tests/twisted/text/receipts.py b/tests/twisted/text/receipts.py
new file mode 100644
index 000000000..45c7d9f89
--- /dev/null
+++ b/tests/twisted/text/receipts.py
@@ -0,0 +1,197 @@
+# coding=utf-8
+"""
+Test XEP-0184 receipts.
+"""
+
+from servicetest import (
+ EventPattern, assertEquals, assertLength, sync_dbus, wrap_channel,
+)
+from gabbletest import exec_test, elem, sync_stream, acknowledge_iq
+import constants as cs
+import ns
+
+import caps_helper
+import rostertest
+from presence.invisible_helper import Xep0186Stream
+
+GUYBRUSH = 'guybrush@mi.lit'
+GUYBRUSH_FULL_JID = GUYBRUSH + '/Sea Cucumber'
+
+def send_received_report(stream, jid, received_id):
+ stream.send(
+ elem('message', from_=jid)(
+ elem(ns.RECEIPTS, 'received', id=received_id)
+ ))
+
+def report_received_on_open_channel(q, bus, conn, stream, chan):
+ received_id = 'fine-leather-jackets'
+
+ send_received_report(stream, GUYBRUSH, received_id)
+ e = q.expect('dbus-signal', signal='MessageReceived', path=chan.object_path)
+ message, = e.args
+ header, = message
+
+ assertEquals(cs.MT_DELIVERY_REPORT, header['message-type'])
+ assertEquals(cs.DELIVERY_STATUS_DELIVERED, header['delivery-status'])
+ assertEquals(received_id, header['delivery-token'])
+
+def report_ignored_without_channel(q, bus, conn, stream):
+ q.forbid_events([EventPattern('dbus-signal', signal='MessageReceived')])
+ send_received_report(stream, 'marley@mi.gov', 'only-one-candidate')
+ sync_dbus(bus, q, conn)
+ q.unforbid_all()
+
+def not_sending_request_to_contact(q, bus, conn, stream, chan):
+ message = [
+ { 'message-type': cs.MT_NORMAL,
+ },
+ { 'content-type': 'text/plain',
+ 'content': 'Mancomb Seepgood?',
+ }]
+ chan.Messages.SendMessage(message, 0)
+
+ e = q.expect('stream-message', to=GUYBRUSH)
+ assertLength(0, list(e.stanza.elements(uri=ns.RECEIPTS, name='request')))
+
+def sending_request_to_presenceless_contact(q, bus, conn, stream, chan):
+ """
+ Initially we know nothing of Guybrush's presence, so should just try our
+ level best if asked to.
+ """
+ message = [
+ { 'message-type': cs.MT_NORMAL,
+ },
+ { 'content-type': 'text/plain',
+ 'content': 'Thriftweed?',
+ }]
+ chan.Messages.SendMessage(message, cs.MSG_SENDING_FLAGS_REPORT_DELIVERY)
+
+ e = q.expect('stream-message', to=GUYBRUSH)
+ assertLength(1, list(e.stanza.elements(uri=ns.RECEIPTS, name='request')))
+
+def sending_request_to_cappy_contact(q, bus, conn, stream, chan):
+ """
+ Test that Gabble requests a receipt from a contact whom we know supports
+ this extension, but only if asked.
+ """
+
+ # Convince Gabble that Guybrush supports this extension
+ caps = { 'node': 'http://whatever',
+ 'ver': caps_helper.compute_caps_hash([], [ns.RECEIPTS], {}),
+ 'hash': 'sha-1',
+ }
+ caps_helper.presence_and_disco(q, conn, stream, GUYBRUSH_FULL_JID,
+ disco=True,
+ client=caps['node'],
+ caps=caps,
+ features=[ns.RECEIPTS])
+ sync_stream(q, stream)
+
+ # Don't ask, don't tell — even if we know Guybrush does support this.
+ not_sending_request_to_contact(q, bus, conn, stream, chan)
+
+ # Ask, tell.
+ message = [
+ { 'message-type': cs.MT_NORMAL,
+ },
+ { 'content-type': 'text/plain',
+ 'content': 'Ulysses?',
+ }]
+ chan.Messages.SendMessage(message, cs.MSG_SENDING_FLAGS_REPORT_DELIVERY)
+
+ e = q.expect('stream-message', to=GUYBRUSH)
+ assertLength(1, list(e.stanza.elements(uri=ns.RECEIPTS, name='request')))
+
+def replying_to_requests(q, bus, conn, stream):
+ jid = 'lechuck@lucasarts.lit'
+
+ # We shouldn't send receipts to people who aren't on our roster.
+ q.forbid_events([EventPattern('stream-message', to=jid)])
+ stream.send(
+ elem('message', from_=jid, type='chat', id='alpha')(
+ elem('body')(
+ u"You didn't kill me, you moron!"
+ ),
+ elem(ns.RECEIPTS, 'request')
+ ))
+
+ q.expect('dbus-signal', signal='MessageReceived')
+ sync_stream(q, stream)
+ q.unforbid_all()
+
+ # We should send receipts to people on our roster, seeing as we're not
+ # invisible.
+ rostertest.send_roster_push(stream, jid, subscription='from')
+
+ stream.send(
+ elem('message', from_=jid, type='chat', id='beta')(
+ elem('body')(
+ u"You've just destroyed my spiritual essences."
+ ),
+ elem(ns.RECEIPTS, 'request')
+ ))
+ q.expect('dbus-signal', signal='MessageReceived')
+ e = q.expect('stream-message', to=jid)
+ receipt = e.stanza.elements(uri=ns.RECEIPTS, name='received').next()
+ assertEquals('beta', receipt['id'])
+
+ # We would like requests in messages without id=''s not to crash Gabble,
+ # and also for it not to send a reply.
+ q.forbid_events([EventPattern('stream-message', to=jid)])
+ stream.send(
+ elem('message', from_=jid, type='chat')( # NB. no id='' attribute
+ elem('body')(
+ u"A favor that I shall now return!"
+ ),
+ elem(ns.RECEIPTS, 'request')
+ ))
+ q.expect('dbus-signal', signal='MessageReceived')
+ sync_stream(q, stream)
+ q.unforbid_all()
+
+ # If we're invisible, LeChuck shouldn't get receipts.
+ conn.SimplePresence.SetPresence("hidden", "")
+ event = q.expect('stream-iq', query_name='invisible')
+ acknowledge_iq(stream, event.stanza)
+
+ q.forbid_events([EventPattern('stream-message', to=jid)])
+ stream.send(
+ elem('message', from_=jid, type='chat', id='epsilon')(
+ elem('body')(
+ u"… but where am I going to find a duck wearing burlap chaps?"
+ ),
+ elem(ns.RECEIPTS, 'request')
+ ))
+ q.expect('dbus-signal', signal='MessageReceived')
+ sync_stream(q, stream)
+ q.unforbid_all()
+
+def test(q, bus, conn, stream):
+ path = conn.Requests.CreateChannel(
+ { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT,
+ cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT,
+ cs.TARGET_ID: GUYBRUSH,
+ })[0]
+ chan = wrap_channel(bus.get_object(conn.bus_name, path), 'Text',
+ ['Messages'])
+
+ # Let's start out with an empty roster, eh?
+ e = q.expect('stream-iq', iq_type='get', query_ns=ns.ROSTER)
+ e.stanza['type'] = 'result'
+ stream.send(e.stanza)
+
+ report_received_on_open_channel(q, bus, conn, stream, chan)
+ report_ignored_without_channel(q, bus, conn, stream)
+ not_sending_request_to_contact(q, bus, conn, stream, chan)
+
+ # FIXME: This test is disabled because of stupidity in the presence cache.
+ # See the comment in receipts_conceivably_supported().
+ #sending_request_to_presenceless_contact(q, bus, conn, stream, chan)
+
+ sending_request_to_cappy_contact(q, bus, conn, stream, chan)
+
+ replying_to_requests(q, bus, conn, stream)
+
+
+if __name__ == '__main__':
+ exec_test(test, protocol=Xep0186Stream)