def test(q, bus, conn, stream, queued=False): event = q.expect('stream-iq', query_ns=ns.ROSTER) event.stanza['type'] = 'result' item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'none' quux_handle = conn.get_contact_handle_sync('*****@*****.**') stream.send(event.stanza) # slight implementation detail: TpBaseContactList emits ContactsChanged # before it announces its channels q.expect('dbus-signal', signal='ContactsChangedWithID', interface=cs.CONN_IFACE_CONTACT_LIST, path=conn.object_path, args=[{quux_handle: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_NO, '')}, {quux_handle: '*****@*****.**'}, {}]) check_contact_roster(conn, '*****@*****.**', [], cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_NO) if queued: conn.Aliasing.SetAliases({quux_handle: 'Quux'}) set_aliases = q.expect('stream-iq', query_ns=ns.ROSTER) item = set_aliases.query.firstChildElement() assertEquals('*****@*****.**', item['jid']) assertEquals('Quux', item['name']) expectations = [ EventPattern('stream-iq', iq_type='set', query_ns=ns.ROSTER), ] call_async(q, conn.ContactList, 'RemoveContacts', [quux_handle]) if queued: # finish off the previous thing we were doing, so removal can proceed acknowledge_iq(stream, set_aliases.stanza) event = q.expect_many(*expectations)[0] item = event.query.firstChildElement() assertEquals('*****@*****.**', item['jid']) assertEquals('remove', item['subscription']) send_roster_push(stream, '*****@*****.**', 'remove') q.expect_many( EventPattern('dbus-signal', interface=cs.CONN_IFACE_CONTACT_LIST, path=conn.object_path, signal='ContactsChangedWithID', args=[{}, {}, {quux_handle: '*****@*****.**'}]), EventPattern('stream-iq', iq_id='push', iq_type='result'), ) acknowledge_iq(stream, event.stanza) q.expect('dbus-return', method='RemoveContacts')
def check_roster_write(stream, event, jid, name): item = event.query.firstChildElement() assertEquals(jid, item['jid']) # This copes with name=None assertEquals(name, item.getAttribute('name')) acknowledge_iq(stream, event.stanza) # RFC 3921 requires the server to send a roster push to all connected # resources whenever a resource updates the roster. Gabble depends on this # and pays no attention to its own nick update until the server sends a # push. send_roster_push(stream, jid, 'none', name=name)
def replying_to_requests(q, bus, conn, stream): jid = '*****@*****.**' # 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 = next(e.stanza.elements(uri=ns.RECEIPTS, name='received')) 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, remove=False): call_async(q, conn.ContactList, 'GetContactListAttributes', [], False) q.expect('dbus-error', method='GetContactListAttributes', name=cs.NOT_YET) event = q.expect('stream-iq', query_ns=ns.ROSTER) item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'both' event.stanza['type'] = 'result' stream.send(event.stanza) holly, dave, arnold, kristine, cat = conn.get_contact_handles_sync( ['*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**']) # slight implementation detail: TpBaseContactList emits ContactsChangedWithID # before it announces its channels s = q.expect('dbus-signal', signal='ContactsChangedWithID', interface=cs.CONN_IFACE_CONTACT_LIST, path=conn.object_path) assertEquals([{ holly: (cs.SUBSCRIPTION_STATE_YES, cs.SUBSCRIPTION_STATE_YES, ''), }, { holly: '*****@*****.**' }, {}], s.args) # this is emitted last, so clients can tell when the initial state dump # has finished q.expect('dbus-signal', signal='ContactListStateChanged', args=[cs.CONTACT_LIST_STATE_SUCCESS]) call_async(q, conn.ContactList, 'GetContactListAttributes', [], False) r = q.expect('dbus-return', method='GetContactListAttributes') assertEquals(({ holly: { cs.CONN_IFACE_CONTACT_LIST + '/publish': cs.SUBSCRIPTION_STATE_YES, cs.CONN_IFACE_CONTACT_LIST + '/subscribe': cs.SUBSCRIPTION_STATE_YES, cs.CONN + '/contact-id': '*****@*****.**', } },), r.value) # publication authorized for Dave, Holly (the former is pre-authorization, # the latter is a no-op) call_async(q, conn.ContactList, 'AuthorizePublication', [dave, holly]) event = q.expect('dbus-return', method='AuthorizePublication') # Receive authorization requests from the contacts # We pre-authorized Dave, so this is automatically approved presence = domish.Element(('jabber:client', 'presence')) presence['type'] = 'subscribe' presence['from'] = '*****@*****.**' stream.send(presence) q.expect_many( EventPattern('dbus-signal', signal='ContactsChangedWithID', args=[{dave: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_ASK, '')}, { dave: '*****@*****.**' }, {}]), EventPattern('stream-presence', presence_type='subscribed', to='*****@*****.**'), ) # Our server responds to Dave being authorized send_roster_push(stream, '*****@*****.**', 'from') q.expect_many( EventPattern('stream-iq', iq_type='result', iq_id='push'), EventPattern('dbus-signal', signal='ContactsChangedWithID', args=[{dave: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_YES, '')}, { dave: '*****@*****.**' }, {}]), ) # The request from Kristine needs authorization (below) presence['from'] = '*****@*****.**' stream.send(presence) q.expect('dbus-signal', signal='ContactsChangedWithID', args=[{kristine: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_ASK, '')}, { kristine: '*****@*****.**' }, {}]) # This request from Arnold is dealt with below presence['from'] = '*****@*****.**' stream.send(presence) q.expect('dbus-signal', signal='ContactsChangedWithID', args=[{arnold: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_ASK, '')}, { arnold: '*****@*****.**' }, {}]) returning_method = 'AuthorizePublication' call_async(q, conn.ContactList, 'AuthorizePublication', [kristine, holly]) q.expect_many( EventPattern('dbus-return', method=returning_method), EventPattern('stream-presence', presence_type='subscribed', to='*****@*****.**'), ) # Our server acknowledges that we authorized Kristine. Holly's state # does not change. send_roster_push(stream, '*****@*****.**', 'from') q.expect_many( EventPattern('dbus-signal', signal='ContactsChangedWithID', args=[{kristine: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_YES, '')}, { kristine: '*****@*****.**' }, {}]), EventPattern('stream-iq', iq_type='result', iq_id='push'), ) # Arnold gives up waiting for us, and cancels his request presence['from'] = '*****@*****.**' presence['type'] = 'unsubscribe' stream.send(presence) q.expect_many( EventPattern('dbus-signal', signal='ContactsChangedWithID', args=[{arnold: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_REMOVED_REMOTELY, '')}, { arnold: '*****@*****.**' }, {}]), EventPattern('stream-presence', presence_type='unsubscribed', to='*****@*****.**'), ) # We can acknowledge that with RemoveContacts or with Unpublish. # The old Chan.T.ContactList API can't acknowledge RemovedRemotely, # because it sees it as "not there at all" and the group logic drops # the "redundant" request. if remove: returning_method = 'RemoveContacts' call_async(q, conn.ContactList, 'RemoveContacts', [arnold]) else: returning_method = 'Unpublish' call_async(q, conn.ContactList, 'Unpublish', [arnold]) # Even if we Unpublish() here, Arnold was never on our XMPP roster, # so setting his publish state to SUBSCRIPTION_STATE_NO should result # in his removal. q.expect_many( EventPattern('dbus-return', method=returning_method), EventPattern('dbus-signal', signal='ContactsChangedWithID', args=[{}, {}, {arnold: '*****@*****.**' }]), ) # Rejecting an authorization request also works presence = domish.Element(('jabber:client', 'presence')) presence['type'] = 'subscribe' presence['from'] = '*****@*****.**' stream.send(presence) q.expect('dbus-signal', signal='ContactsChangedWithID', args=[{cat: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_ASK, '')}, { cat: '*****@*****.**' }, {}]) if remove: returning_method = 'RemoveContacts' call_async(q, conn.ContactList, 'RemoveContacts', [cat]) else: returning_method = 'Unpublish' call_async(q, conn.ContactList, 'Unpublish', [cat]) # As above, the only reason the Cat is on our contact list is the pending # publish request, so Unpublish really results in removal. q.expect_many( EventPattern('dbus-return', method=returning_method), EventPattern('dbus-signal', signal='ContactsChangedWithID', args=[{}, {}, { cat: '*****@*****.**' }]), ) # Redundant API calls (removing an absent contact, etc.) cause no network # traffic, and succeed. forbidden = [EventPattern('stream-iq', query_ns=ns.ROSTER), EventPattern('stream-presence')] sync_stream(q, stream) sync_dbus(bus, q, conn) q.forbid_events(forbidden) call_async(q, conn.ContactList, 'AuthorizePublication', [kristine, holly, dave]) call_async(q, conn.ContactList, 'Unpublish', [arnold, cat]) call_async(q, conn.ContactList, 'RemoveContacts', [arnold, cat]) q.expect_many( EventPattern('dbus-return', method='AuthorizePublication'), EventPattern('dbus-return', method='Unpublish'), EventPattern('dbus-return', method='RemoveContacts'), ) sync_stream(q, stream) sync_dbus(bus, q, conn) q.unforbid_events(forbidden) # There's one more case: revoking the publish permission of someone who is # genuinely on the roster. if remove: returning_method = 'RemoveContacts' call_async(q, conn.ContactList, 'RemoveContacts', [holly]) else: returning_method = 'Unpublish' call_async(q, conn.ContactList, 'Unpublish', [holly]) if remove: iq = q.expect('stream-iq', iq_type='set', query_ns=ns.ROSTER, query_name='query') acknowledge_iq(stream, iq.stanza) q.expect('dbus-return', method='RemoveContacts') # FIXME: when we depend on a new enough tp-glib, expect RemoveMembers # to return here too send_roster_push(stream, '*****@*****.**', 'remove') q.expect_many( EventPattern('stream-iq', iq_type='result', iq_id='push'), EventPattern('dbus-signal', signal='ContactsChangedWithID', args=[{}, {}, { holly: '*****@*****.**' }]), ) else: q.expect_many( EventPattern('dbus-return', method=returning_method), EventPattern('stream-presence', presence_type='unsubscribed', to='*****@*****.**'), ) send_roster_push(stream, '*****@*****.**', 'to') q.expect_many( EventPattern('stream-iq', iq_type='result', iq_id='push'), EventPattern('dbus-signal', signal='ContactsChangedWithID', args=[{holly: (cs.SUBSCRIPTION_STATE_YES, cs.SUBSCRIPTION_STATE_NO, ''), }, { holly: '*****@*****.**' }, {}]), )
def test(q, bus, conn, stream): event, event2 = q.expect_many( EventPattern('stream-iq', to=None, query_ns='vcard-temp', query_name='vCard'), EventPattern('stream-iq', query_ns=ns.ROSTER)) acknowledge_iq(stream, event.stanza) acknowledge_iq(stream, event2.stanza) q.expect('dbus-signal', signal='ContactListStateChanged', args=[cs.CONTACT_LIST_STATE_SUCCESS]) # request subscription handle = conn.get_contact_handle_sync('*****@*****.**') call_async(q, conn.ContactList, 'RequestSubscription', [handle], '') event = q.expect('stream-iq', iq_type='set', query_ns=ns.ROSTER) acknowledge_iq(stream, event.stanza) q.expect('dbus-return', method='RequestSubscription') call_async(q, conn.Aliasing, 'RequestAliases', [handle]) event = q.expect('stream-iq', iq_type='get', query_ns='http://jabber.org/protocol/pubsub', to='*****@*****.**') send_pep_nick_reply(stream, event.stanza, 'Bobby') event, _ = q.expect_many( EventPattern('stream-iq', iq_type='set', query_ns=ns.ROSTER), EventPattern('dbus-return', method='RequestAliases', value=(['Bobby'], ))) check_roster_write(stream, event, '*****@*****.**', 'Bobby') # We get a roster push for a contact who for some reason has their alias # set on our roster to the empty string (maybe a buggy client?). It's never # useful for Gabble to say that someone's alias is the empty string (given # the current semantics where the alias is always meant to be something you # could show, even if it's just their JID), so let's forbid that. jid = '*****@*****.**' handle = conn.get_contact_handle_sync(jid) q.forbid_events([ EventPattern('dbus-signal', signal='AliasesChanged', args=[[(handle, '')]]) ]) send_roster_push(stream, jid, 'both', name='') # I don't really have very strong opinions on whether Gabble should be # signalling that this contact's alias has *changed* per se, so am not # explicitly expecting that. q.expect('dbus-signal', signal='MembersChangedDetailed') # But if we ask for it, Gabble should probably send a PEP query. h2asv = conn.Contacts.GetContactAttributes([handle], [cs.CONN_IFACE_ALIASING], False) assertEquals(jid, h2asv[handle][cs.ATTR_ALIAS]) event = q.expect('stream-iq', iq_type='get', query_ns=ns.PUBSUB, to=jid) nick = 'Constant Future' send_pep_nick_reply(stream, event.stanza, nick) expect_AliasesChanged_and_roster_write(q, stream, handle, jid, nick) # Here's another contact, whose alias is set on our roster to their JID: # because we've cached that they have no alias. Gabble shouldn't make # unsolicited PEP or vCard queries to them. jid = '*****@*****.**' handle = conn.get_contact_handle_sync(jid) q.forbid_events([ EventPattern('stream-iq', query_ns=ns.PUBSUB, to=jid), EventPattern('stream-iq', query_ns=ns.VCARD_TEMP, to=jid), ]) send_roster_push(stream, jid, 'both', name=jid) q.expect('dbus-signal', signal='AliasesChanged', args=[[(handle, jid)]]) sync_stream(q, stream) # But if we get a PEP nickname update for this contact, Gabble should use # the new nickname, and write it back to the roster. nick = u'The Friendly Faith Plate' stream.send(make_pubsub_event(jid, ns.NICK, elem(ns.NICK, 'nick')(nick))) expect_AliasesChanged_and_roster_write(q, stream, handle, jid, nick) # As an undocumented extension, we treat setting the alias to the empty # string to mean "whatever the contact says their nickname is". (The rest # of this test is a regression test for # <https://bugs.freedesktop.org/show_bug.cgi?id=11321>.) # # So first up, let's change the Friendly Faith Plate's nickname to # something else. custom_nick = u'I saw a deer today' conn.Aliasing.SetAliases({handle: custom_nick}) expect_AliasesChanged_and_roster_write(q, stream, handle, jid, custom_nick) assertEquals([custom_nick], conn.Aliasing.RequestAliases([handle])) # And now set it to the empty string. Since Gabble happens to have a # nickname this contact specified cached, it should switch over to that one. conn.Aliasing.SetAliases({handle: ''}) expect_AliasesChanged_and_roster_write(q, stream, handle, jid, nick) assertEquals([nick], conn.Aliasing.RequestAliases([handle])) # Here's a contact we haven't seen before, pushed to our roster with a # nickname already there. jid = '*****@*****.**' handle = conn.get_contact_handle_sync(jid) nick = 'Potato' send_roster_push(stream, jid, 'both', name=nick) q.expect('dbus-signal', signal='AliasesChanged', args=[[(handle, nick)]]) # If the user clears their alias, we should expect Gabble to say over D-Bus # that their nickname is their jid, and send a roster push removing the # name='' attribute... conn.Aliasing.SetAliases({handle: ''}) expect_AliasesChanged_and_roster_write(q, stream, handle, jid, None) # ...and also send a PEP query to find a better nickname; when the contact # replies, Gabble should update the roster accordingly. event = q.expect('stream-iq', iq_type='get', query_ns=ns.PUBSUB, to=jid) send_pep_nick_reply(stream, event.stanza, 'GLaDOS') expect_AliasesChanged_and_roster_write(q, stream, handle, jid, 'GLaDOS')
def replying_to_requests(q, bus, conn, stream): jid = '*****@*****.**' # 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): event, event2 = q.expect_many( EventPattern('stream-iq', to=None, query_ns='vcard-temp', query_name='vCard'), EventPattern('stream-iq', query_ns=ns.ROSTER)) acknowledge_iq(stream, event.stanza) acknowledge_iq(stream, event2.stanza) q.expect('dbus-signal', signal='ContactListStateChanged', args=[cs.CONTACT_LIST_STATE_SUCCESS]) # request subscription handle = conn.get_contact_handle_sync('*****@*****.**') call_async(q, conn.ContactList, 'RequestSubscription', [handle], '') event = q.expect('stream-iq', iq_type='set', query_ns=ns.ROSTER) acknowledge_iq(stream, event.stanza) q.expect('dbus-return', method='RequestSubscription') call_async(q, conn.Aliasing, 'RequestAliases', [handle]) event = q.expect('stream-iq', iq_type='get', query_ns='http://jabber.org/protocol/pubsub', to='*****@*****.**') send_pep_nick_reply(stream, event.stanza, 'Bobby') event, _ = q.expect_many( EventPattern('stream-iq', iq_type='set', query_ns=ns.ROSTER), EventPattern('dbus-return', method='RequestAliases', value=(['Bobby'],))) check_roster_write(stream, event, '*****@*****.**', 'Bobby') # We get a roster push for a contact who for some reason has their alias # set on our roster to the empty string (maybe a buggy client?). It's never # useful for Gabble to say that someone's alias is the empty string (given # the current semantics where the alias is always meant to be something you # could show, even if it's just their JID), so let's forbid that. jid = '*****@*****.**' handle = conn.get_contact_handle_sync(jid) q.forbid_events([EventPattern('dbus-signal', signal='AliasesChanged', args=[[(handle, '')]])]) send_roster_push(stream, jid, 'both', name='') # I don't really have very strong opinions on whether Gabble should be # signalling that this contact's alias has *changed* per se, so am not # explicitly expecting that. q.expect('dbus-signal', signal='MembersChangedDetailed') # But if we ask for it, Gabble should probably send a PEP query. h2asv = conn.Contacts.GetContactAttributes([handle], [cs.CONN_IFACE_ALIASING], False) assertEquals(jid, h2asv[handle][cs.ATTR_ALIAS]) event = q.expect('stream-iq', iq_type='get', query_ns=ns.PUBSUB, to=jid) nick = 'Constant Future' send_pep_nick_reply(stream, event.stanza, nick) expect_AliasesChanged_and_roster_write(q, stream, handle, jid, nick) # Here's another contact, whose alias is set on our roster to their JID: # because we've cached that they have no alias. Gabble shouldn't make # unsolicited PEP or vCard queries to them. jid = '*****@*****.**' handle = conn.get_contact_handle_sync(jid) q.forbid_events([ EventPattern('stream-iq', query_ns=ns.PUBSUB, to=jid), EventPattern('stream-iq', query_ns=ns.VCARD_TEMP, to=jid), ]) send_roster_push(stream, jid, 'both', name=jid) q.expect('dbus-signal', signal='AliasesChanged', args=[[(handle, jid)]]) sync_stream(q, stream) # But if we get a PEP nickname update for this contact, Gabble should use # the new nickname, and write it back to the roster. nick = u'The Friendly Faith Plate' stream.send(make_pubsub_event(jid, ns.NICK, elem(ns.NICK, 'nick')(nick))) expect_AliasesChanged_and_roster_write(q, stream, handle, jid, nick) # As an undocumented extension, we treat setting the alias to the empty # string to mean "whatever the contact says their nickname is". (The rest # of this test is a regression test for # <https://bugs.freedesktop.org/show_bug.cgi?id=11321>.) # # So first up, let's change the Friendly Faith Plate's nickname to # something else. custom_nick = u'I saw a deer today' conn.Aliasing.SetAliases({handle: custom_nick}) expect_AliasesChanged_and_roster_write(q, stream, handle, jid, custom_nick) assertEquals([custom_nick], conn.Aliasing.RequestAliases([handle])) # And now set it to the empty string. Since Gabble happens to have a # nickname this contact specified cached, it should switch over to that one. conn.Aliasing.SetAliases({handle: ''}) expect_AliasesChanged_and_roster_write(q, stream, handle, jid, nick) assertEquals([nick], conn.Aliasing.RequestAliases([handle])) # Here's a contact we haven't seen before, pushed to our roster with a # nickname already there. jid = '*****@*****.**' handle = conn.get_contact_handle_sync(jid) nick = 'Potato' send_roster_push(stream, jid, 'both', name=nick) q.expect('dbus-signal', signal='AliasesChanged', args=[[(handle, nick)]]) # If the user clears their alias, we should expect Gabble to say over D-Bus # that their nickname is their jid, and send a roster push removing the # name='' attribute... conn.Aliasing.SetAliases({handle: ''}) expect_AliasesChanged_and_roster_write(q, stream, handle, jid, None) # ...and also send a PEP query to find a better nickname; when the contact # replies, Gabble should update the roster accordingly. event = q.expect('stream-iq', iq_type='get', query_ns=ns.PUBSUB, to=jid) send_pep_nick_reply(stream, event.stanza, 'GLaDOS') expect_AliasesChanged_and_roster_write(q, stream, handle, jid, 'GLaDOS')
def test(q, bus, conn, stream, remove=False, remote='accept'): event = q.expect('stream-iq', query_ns=ns.ROSTER) # send back empty roster event.stanza['type'] = 'result' stream.send(event.stanza) q.expect('dbus-signal', signal='ContactListStateChanged', args=[cs.CONTACT_LIST_STATE_SUCCESS]) # request subscription alice, bob = conn.get_contact_handles_sync( ['*****@*****.**', '*****@*****.**']) # Repeated subscription requests are *not* idempotent: the second request # should nag the contact again. for first_time in True, False, False: call_async(q, conn.ContactList, 'RequestSubscription', [bob], 'plz add kthx') if first_time: event = q.expect('stream-iq', iq_type='set', query_ns=ns.ROSTER) item = event.query.firstChildElement() assertEquals('*****@*****.**', item["jid"]) acknowledge_iq(stream, event.stanza) expectations = [ EventPattern('stream-presence', presence_type='subscribe'), ] expectations.append( EventPattern('dbus-return', method='RequestSubscription')) event = q.expect_many(*expectations)[0] assertEquals('plz add kthx', event.presence_status) if first_time: # Our server sends a roster push indicating that yes, we added him send_roster_push(stream, '*****@*****.**', 'none') q.expect('stream-iq', iq_type='result', iq_id='push') # Our server will also send a roster push with the ask=subscribe # sub-state, in response to our <presence type=subscribe>. # (RFC 3921 §8.2.4) send_roster_push(stream, '*****@*****.**', 'none', True) q.expect('stream-iq', iq_type='result', iq_id='push') if remote == 'reject': # Bob rejects our request. presence = domish.Element(('jabber:client', 'presence')) presence['from'] = '*****@*****.**' presence['type'] = 'unsubscribed' stream.send(presence) q.expect_many( EventPattern( 'dbus-signal', signal='MembersChangedDetailed', predicate=lambda e: e.args[0] == [] and e.args[1] == [bob] and e.args[2] == [] and e.args[3] == [] and e.args[4][ 'change-reason'] == cs.GC_REASON_PERMISSION_DENIED), #EventPattern('stream-presence'), EventPattern('dbus-signal', signal='ContactsChangedWithID', args=[{ bob: (cs.SUBSCRIPTION_STATE_REMOVED_REMOTELY, cs.SUBSCRIPTION_STATE_NO, ''), }, { bob: '*****@*****.**' }, {}]), ) send_roster_push(stream, '*****@*****.**', 'to') q.expect('stream-iq', iq_type='result', iq_id='push') else: # Bob accepts presence = domish.Element(('jabber:client', 'presence')) presence['from'] = '*****@*****.**' presence['type'] = 'subscribed' stream.send(presence) q.expect_many( EventPattern('dbus-signal', signal='MembersChangedDetailed', predicate=lambda e: e.args[0] == [bob] and e.args[1] == [] and e.args[2] == [] and e.args[3] == []), EventPattern('stream-presence'), EventPattern('dbus-signal', signal='ContactsChangedWithID', args=[{ bob: (cs.SUBSCRIPTION_STATE_YES, cs.SUBSCRIPTION_STATE_NO, ''), }, { bob: '*****@*****.**' }, {}]), ) send_roster_push(stream, '*****@*****.**', 'to') q.expect('stream-iq', iq_type='result', iq_id='push') # Doing the same again is a successful no-op forbidden = [ EventPattern('stream-iq', query_ns=ns.ROSTER), EventPattern('stream-presence') ] sync_stream(q, stream) sync_dbus(bus, q, conn) q.forbid_events(forbidden) call_async(q, conn.ContactList, 'RequestSubscription', [bob], 'moo') q.expect('dbus-return', method='RequestSubscription') # Alice is not on the list call_async(q, conn.ContactList, 'Unsubscribe', [alice]) q.expect('dbus-return', method='Unsubscribe') call_async(q, conn.ContactList, 'RemoveContacts', [alice]) q.expect('dbus-return', method='RemoveContacts') sync_stream(q, stream) sync_dbus(bus, q, conn) q.unforbid_events(forbidden) if remote == 'revoke': # After accepting us, Bob then removes us. presence = domish.Element(('jabber:client', 'presence')) presence['from'] = '*****@*****.**' presence['type'] = 'unsubscribed' stream.send(presence) q.expect_many( EventPattern( 'dbus-signal', signal='MembersChangedDetailed', predicate=lambda e: e.args[0] == [] and e.args[1] == [bob] and e.args[2] == [] and e.args[3] == [] and e.args[4][ 'change-reason'] == cs.GC_REASON_PERMISSION_DENIED), EventPattern('stream-presence'), EventPattern('dbus-signal', signal='ContactsChangedWithID', args=[{ bob: (cs.SUBSCRIPTION_STATE_REMOVED_REMOTELY, cs.SUBSCRIPTION_STATE_NO, ''), }, { bob: '*****@*****.**' }, {}]), ) # Else, Bob isn't actually as interesting as we thought. Never mind, # we can unsubscribe or remove him (below), with the same APIs we'd # use to acknowledge remote removal. # (Unsubscribing from pending-subscribe is tested in # roster/removed-from-rp-subscribe.py so we don't test it here.) if remove: returning_method = 'RemoveContacts' call_async(q, conn.ContactList, 'RemoveContacts', [bob]) else: returning_method = 'Unsubscribe' call_async(q, conn.ContactList, 'Unsubscribe', [bob]) if remove: iq = q.expect('stream-iq', iq_type='set', query_ns=ns.ROSTER, query_name='query') acknowledge_iq(stream, iq.stanza) q.expect('dbus-return', method='RemoveContacts') # FIXME: when we depend on a new enough tp-glib, expect RemoveMembers # to return here too send_roster_push(stream, '*****@*****.**', 'remove') q.expect_many( EventPattern('stream-iq', iq_type='result', iq_id='push'), EventPattern('dbus-signal', signal='ContactsChangedWithID', args=[{}, {}, { bob: '*****@*****.**' }]), ) else: q.expect_many( EventPattern('dbus-return', method=returning_method), EventPattern('stream-presence', presence_type='unsubscribe', to='*****@*****.**'), ) send_roster_push(stream, '*****@*****.**', 'none') q.expect_many( EventPattern('stream-iq', iq_type='result', iq_id='push'), EventPattern('dbus-signal', signal='ContactsChangedWithID', args=[{ bob: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_NO, ''), }, { bob: '*****@*****.**' }, {}]), )
def test(q, bus, conn, stream, queued=False): event = q.expect('stream-iq', query_ns=ns.ROSTER) event.stanza['type'] = 'result' item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'none' quux_handle = conn.get_contact_handle_sync('*****@*****.**') stream.send(event.stanza) # slight implementation detail: TpBaseContactList emits ContactsChanged # before it announces its channels q.expect('dbus-signal', signal='ContactsChangedWithID', interface=cs.CONN_IFACE_CONTACT_LIST, path=conn.object_path, args=[{ quux_handle: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_NO, '') }, { quux_handle: '*****@*****.**' }, {}]) check_contact_roster(conn, '*****@*****.**', [], cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_NO) if queued: conn.Aliasing.SetAliases({quux_handle: 'Quux'}) set_aliases = q.expect('stream-iq', query_ns=ns.ROSTER) item = set_aliases.query.firstChildElement() assertEquals('*****@*****.**', item['jid']) assertEquals('Quux', item['name']) expectations = [ EventPattern('stream-iq', iq_type='set', query_ns=ns.ROSTER), ] call_async(q, conn.ContactList, 'RemoveContacts', [quux_handle]) if queued: # finish off the previous thing we were doing, so removal can proceed acknowledge_iq(stream, set_aliases.stanza) event = q.expect_many(*expectations)[0] item = event.query.firstChildElement() assertEquals('*****@*****.**', item['jid']) assertEquals('remove', item['subscription']) send_roster_push(stream, '*****@*****.**', 'remove') q.expect_many( EventPattern('dbus-signal', interface=cs.CONN_IFACE_CONTACT_LIST, path=conn.object_path, signal='ContactsChangedWithID', args=[{}, {}, { quux_handle: '*****@*****.**' }]), EventPattern('stream-iq', iq_id='push', iq_type='result'), ) acknowledge_iq(stream, event.stanza) q.expect('dbus-return', method='RemoveContacts')
def test(q, bus, conn, stream, modern=True, queued=False): event = q.expect('stream-iq', query_ns=ns.ROSTER) event.stanza['type'] = 'result' item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'none' quux_handle = conn.RequestHandles(cs.HT_CONTACT, ['*****@*****.**'])[0] stream.send(event.stanza) # slight implementation detail: TpBaseContactList emits ContactsChanged # before it announces its channels q.expect('dbus-signal', signal='ContactsChanged', interface=cs.CONN_IFACE_CONTACT_LIST, path=conn.object_path, args=[{quux_handle: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_NO, '')}, []]) pairs = expect_contact_list_signals(q, bus, conn, ['publish', 'subscribe', 'stored']) check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, 'publish', []) check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, 'subscribe', []) stored = check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, 'stored', ['*****@*****.**']) assertLength(0, pairs) # i.e. we've checked all of them if queued: conn.Aliasing.SetAliases({quux_handle: 'Quux'}) set_aliases = q.expect('stream-iq', query_ns=ns.ROSTER) item = set_aliases.query.firstChildElement() assertEquals('*****@*****.**', item['jid']) assertEquals('Quux', item['name']) expectations = [ EventPattern('stream-iq', iq_type='set', query_ns=ns.ROSTER), ] if modern: call_async(q, conn.ContactList, 'RemoveContacts', [quux_handle]) else: call_async(q, stored.Group, 'RemoveMembers', [quux_handle], '') if queued: # finish off the previous thing we were doing, so removal can proceed acknowledge_iq(stream, set_aliases.stanza) event = q.expect_many(*expectations)[0] item = event.query.firstChildElement() assertEquals('*****@*****.**', item['jid']) assertEquals('remove', item['subscription']) send_roster_push(stream, '*****@*****.**', 'remove') q.expect_many( EventPattern('dbus-signal', interface=cs.CHANNEL_IFACE_GROUP, path=stored.object_path, signal='MembersChanged', args=['', [], [quux_handle], [], [], 0, 0]), EventPattern('dbus-signal', interface=cs.CONN_IFACE_CONTACT_LIST, path=conn.object_path, signal='ContactsChanged', args=[{}, [quux_handle]]), EventPattern('stream-iq', iq_id='push', iq_type='result'), ) acknowledge_iq(stream, event.stanza) if modern: q.expect('dbus-return', method='RemoveContacts')
def test(q, bus, conn, stream, modern=True, remove=False, remote='accept'): event = q.expect('stream-iq', query_ns=ns.ROSTER) # send back empty roster event.stanza['type'] = 'result' stream.send(event.stanza) while True: event = q.expect('dbus-signal', signal='NewChannel') path, type, handle_type, handle, suppress_handler = event.args if type != cs.CHANNEL_TYPE_CONTACT_LIST: continue chan_name = conn.InspectHandles(handle_type, [handle])[0] if chan_name == 'subscribe': break chan = wrap_channel(bus.get_object(conn.bus_name, path), 'ContactList') assertLength(0, chan.Group.GetMembers()) stored_path = conn.Requests.EnsureChannel({ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST, cs.TARGET_HANDLE_TYPE: cs.HT_LIST, cs.TARGET_ID: 'stored', })[1] stored = wrap_channel(bus.get_object(conn.bus_name, stored_path), 'ContactList') # request subscription alice, bob = conn.RequestHandles(cs.HT_CONTACT, ['*****@*****.**', '*****@*****.**']) # Repeated subscription requests are *not* idempotent: the second request # should nag the contact again. for first_time in True, False, False: if modern: call_async(q, conn.ContactList, 'RequestSubscription', [bob], 'plz add kthx') else: call_async(q, chan.Group, 'AddMembers', [bob], 'plz add kthx') if first_time: event = q.expect('stream-iq', iq_type='set', query_ns=ns.ROSTER) item = event.query.firstChildElement() assertEquals('*****@*****.**', item["jid"]) acknowledge_iq(stream, event.stanza) expectations = [ EventPattern('stream-presence', presence_type='subscribe'), ] if modern: expectations.append(EventPattern('dbus-return', method='RequestSubscription')) event = q.expect_many(*expectations)[0] assertEquals('plz add kthx', event.presence_status) if first_time: # Our server sends a roster push indicating that yes, we added him send_roster_push(stream, '*****@*****.**', 'none') q.expect('stream-iq', iq_type='result', iq_id='push') # Our server will also send a roster push with the ask=subscribe # sub-state, in response to our <presence type=subscribe>. # (RFC 3921 §8.2.4) send_roster_push(stream, '*****@*****.**', 'none', True) q.expect('stream-iq', iq_type='result', iq_id='push') if remote == 'reject': # Bob rejects our request. presence = domish.Element(('jabber:client', 'presence')) presence['from'] = '*****@*****.**' presence['type'] = 'unsubscribed' stream.send(presence) q.expect_many( EventPattern('dbus-signal', signal='MembersChanged', args=['', [], [bob], [], [], bob, cs.GC_REASON_PERMISSION_DENIED]), #EventPattern('stream-presence'), EventPattern('dbus-signal', signal='ContactsChanged', args=[{bob: (cs.SUBSCRIPTION_STATE_REMOVED_REMOTELY, cs.SUBSCRIPTION_STATE_NO, ''), }, []]), ) send_roster_push(stream, '*****@*****.**', 'to') q.expect('stream-iq', iq_type='result', iq_id='push') else: # Bob accepts presence = domish.Element(('jabber:client', 'presence')) presence['from'] = '*****@*****.**' presence['type'] = 'subscribed' stream.send(presence) q.expect_many( EventPattern('dbus-signal', signal='MembersChanged', args=['', [bob], [], [], [], bob, 0]), EventPattern('stream-presence'), EventPattern('dbus-signal', signal='ContactsChanged', args=[{bob: (cs.SUBSCRIPTION_STATE_YES, cs.SUBSCRIPTION_STATE_NO, ''), }, []]), ) send_roster_push(stream, '*****@*****.**', 'to') q.expect('stream-iq', iq_type='result', iq_id='push') # Doing the same again is a successful no-op forbidden = [EventPattern('stream-iq', query_ns=ns.ROSTER), EventPattern('stream-presence')] sync_stream(q, stream) sync_dbus(bus, q, conn) q.forbid_events(forbidden) call_async(q, conn.ContactList, 'RequestSubscription', [bob], 'moo') q.expect('dbus-return', method='RequestSubscription') # Alice is not on the list call_async(q, conn.ContactList, 'Unsubscribe', [alice]) q.expect('dbus-return', method='Unsubscribe') call_async(q, conn.ContactList, 'RemoveContacts', [alice]) q.expect('dbus-return', method='RemoveContacts') sync_stream(q, stream) sync_dbus(bus, q, conn) q.unforbid_events(forbidden) if remote == 'revoke': # After accepting us, Bob then removes us. presence = domish.Element(('jabber:client', 'presence')) presence['from'] = '*****@*****.**' presence['type'] = 'unsubscribed' stream.send(presence) q.expect_many( EventPattern('dbus-signal', signal='MembersChanged', args=['', [], [bob], [], [], bob, cs.GC_REASON_PERMISSION_DENIED]), EventPattern('stream-presence'), EventPattern('dbus-signal', signal='ContactsChanged', args=[{bob: (cs.SUBSCRIPTION_STATE_REMOVED_REMOTELY, cs.SUBSCRIPTION_STATE_NO, ''), }, []]), ) # Else, Bob isn't actually as interesting as we thought. Never mind, # we can unsubscribe or remove him (below), with the same APIs we'd # use to acknowledge remote removal. # (Unsubscribing from pending-subscribe is tested in # roster/removed-from-rp-subscribe.py so we don't test it here.) # If Bob removed us, we have to use modern APIs from now on, because from # the point of view of the old Group interface, removed remotely and # removed locally are synonymous. if remote in ('reject', 'revoke'): modern = True if modern: if remove: returning_method = 'RemoveContacts' call_async(q, conn.ContactList, 'RemoveContacts', [bob]) else: returning_method = 'Unsubscribe' call_async(q, conn.ContactList, 'Unsubscribe', [bob]) else: returning_method = 'RemoveMembers' if remove: call_async(q, stored.Group, 'RemoveMembers', [bob], '') else: call_async(q, chan.Group, 'RemoveMembers', [bob], '') if remove: iq = q.expect('stream-iq', iq_type='set', query_ns=ns.ROSTER, query_name='query') acknowledge_iq(stream, iq.stanza) if modern: q.expect('dbus-return', method='RemoveContacts') # FIXME: when we depend on a new enough tp-glib, expect RemoveMembers # to return here too send_roster_push(stream, '*****@*****.**', 'remove') q.expect_many( EventPattern('stream-iq', iq_type='result', iq_id='push'), EventPattern('dbus-signal', signal='ContactsChanged', args=[{}, [bob]]), ) else: q.expect_many( EventPattern('dbus-return', method=returning_method), EventPattern('stream-presence', presence_type='unsubscribe', to='*****@*****.**'), ) send_roster_push(stream, '*****@*****.**', 'none') q.expect_many( EventPattern('stream-iq', iq_type='result', iq_id='push'), EventPattern('dbus-signal', signal='ContactsChanged', args=[{bob: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_NO, ''), }, []]), )
def test(q, bus, conn, stream, remove=False, remote='accept'): event = q.expect('stream-iq', query_ns=ns.ROSTER) # send back empty roster event.stanza['type'] = 'result' stream.send(event.stanza) q.expect('dbus-signal', signal='ContactListStateChanged', args=[cs.CONTACT_LIST_STATE_SUCCESS]) # request subscription alice, bob = conn.get_contact_handles_sync( ['*****@*****.**', '*****@*****.**']) # Repeated subscription requests are *not* idempotent: the second request # should nag the contact again. for first_time in True, False, False: call_async(q, conn.ContactList, 'RequestSubscription', [bob], 'plz add kthx') if first_time: event = q.expect('stream-iq', iq_type='set', query_ns=ns.ROSTER) item = event.query.firstChildElement() assertEquals('*****@*****.**', item["jid"]) acknowledge_iq(stream, event.stanza) expectations = [ EventPattern('stream-presence', presence_type='subscribe'), ] expectations.append(EventPattern('dbus-return', method='RequestSubscription')) event = q.expect_many(*expectations)[0] assertEquals('plz add kthx', event.presence_status) if first_time: # Our server sends a roster push indicating that yes, we added him send_roster_push(stream, '*****@*****.**', 'none') q.expect('stream-iq', iq_type='result', iq_id='push') # Our server will also send a roster push with the ask=subscribe # sub-state, in response to our <presence type=subscribe>. # (RFC 3921 §8.2.4) send_roster_push(stream, '*****@*****.**', 'none', True) q.expect('stream-iq', iq_type='result', iq_id='push') if remote == 'reject': # Bob rejects our request. presence = domish.Element(('jabber:client', 'presence')) presence['from'] = '*****@*****.**' presence['type'] = 'unsubscribed' stream.send(presence) q.expect_many( EventPattern('dbus-signal', signal='MembersChangedDetailed', predicate=lambda e: e.args[0] == [] and e.args[1] == [bob] and e.args[2] == [] and e.args[3] == [] and e.args[4]['change-reason'] == cs.GC_REASON_PERMISSION_DENIED), #EventPattern('stream-presence'), EventPattern('dbus-signal', signal='ContactsChangedWithID', args=[{bob: (cs.SUBSCRIPTION_STATE_REMOVED_REMOTELY, cs.SUBSCRIPTION_STATE_NO, ''), }, {bob: '*****@*****.**'}, {}]), ) send_roster_push(stream, '*****@*****.**', 'to') q.expect('stream-iq', iq_type='result', iq_id='push') else: # Bob accepts presence = domish.Element(('jabber:client', 'presence')) presence['from'] = '*****@*****.**' presence['type'] = 'subscribed' stream.send(presence) q.expect_many( EventPattern('dbus-signal', signal='MembersChangedDetailed', predicate=lambda e: e.args[0] == [bob] and e.args[1] == [] and e.args[2] == [] and e.args[3] == []), EventPattern('stream-presence'), EventPattern('dbus-signal', signal='ContactsChangedWithID', args=[{bob: (cs.SUBSCRIPTION_STATE_YES, cs.SUBSCRIPTION_STATE_NO, ''), }, {bob: '*****@*****.**'}, {}]), ) send_roster_push(stream, '*****@*****.**', 'to') q.expect('stream-iq', iq_type='result', iq_id='push') # Doing the same again is a successful no-op forbidden = [EventPattern('stream-iq', query_ns=ns.ROSTER), EventPattern('stream-presence')] sync_stream(q, stream) sync_dbus(bus, q, conn) q.forbid_events(forbidden) call_async(q, conn.ContactList, 'RequestSubscription', [bob], 'moo') q.expect('dbus-return', method='RequestSubscription') # Alice is not on the list call_async(q, conn.ContactList, 'Unsubscribe', [alice]) q.expect('dbus-return', method='Unsubscribe') call_async(q, conn.ContactList, 'RemoveContacts', [alice]) q.expect('dbus-return', method='RemoveContacts') sync_stream(q, stream) sync_dbus(bus, q, conn) q.unforbid_events(forbidden) if remote == 'revoke': # After accepting us, Bob then removes us. presence = domish.Element(('jabber:client', 'presence')) presence['from'] = '*****@*****.**' presence['type'] = 'unsubscribed' stream.send(presence) q.expect_many( EventPattern('dbus-signal', signal='MembersChangedDetailed', predicate=lambda e: e.args[0] == [] and e.args[1] == [bob] and e.args[2] == [] and e.args[3] == [] and e.args[4]['change-reason'] == cs.GC_REASON_PERMISSION_DENIED), EventPattern('stream-presence'), EventPattern('dbus-signal', signal='ContactsChangedWithID', args=[{bob: (cs.SUBSCRIPTION_STATE_REMOVED_REMOTELY, cs.SUBSCRIPTION_STATE_NO, ''), }, {bob: '*****@*****.**'}, {}]), ) # Else, Bob isn't actually as interesting as we thought. Never mind, # we can unsubscribe or remove him (below), with the same APIs we'd # use to acknowledge remote removal. # (Unsubscribing from pending-subscribe is tested in # roster/removed-from-rp-subscribe.py so we don't test it here.) if remove: returning_method = 'RemoveContacts' call_async(q, conn.ContactList, 'RemoveContacts', [bob]) else: returning_method = 'Unsubscribe' call_async(q, conn.ContactList, 'Unsubscribe', [bob]) if remove: iq = q.expect('stream-iq', iq_type='set', query_ns=ns.ROSTER, query_name='query') acknowledge_iq(stream, iq.stanza) q.expect('dbus-return', method='RemoveContacts') # FIXME: when we depend on a new enough tp-glib, expect RemoveMembers # to return here too send_roster_push(stream, '*****@*****.**', 'remove') q.expect_many( EventPattern('stream-iq', iq_type='result', iq_id='push'), EventPattern('dbus-signal', signal='ContactsChangedWithID', args=[{}, {}, {bob: '*****@*****.**'}]), ) else: q.expect_many( EventPattern('dbus-return', method=returning_method), EventPattern('stream-presence', presence_type='unsubscribe', to='*****@*****.**'), ) send_roster_push(stream, '*****@*****.**', 'none') q.expect_many( EventPattern('stream-iq', iq_type='result', iq_id='push'), EventPattern('dbus-signal', signal='ContactsChangedWithID', args=[{bob: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_NO, ''), }, {bob: '*****@*****.**'}, {}]), )