def test(q, bus, conn, stream): conn.Connect() _, event = q.expect_many( EventPattern('dbus-signal', signal='StatusChanged', args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]), EventPattern('stream-iq', query_ns=ns.ROSTER), ) amy_handle = conn.RequestHandles(1, ['*****@*****.**'])[0] event.stanza['type'] = 'result' item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'both' stream.send(event.stanza) stream.send(make_presence('*****@*****.**', show='away', status='At the pub')) q.expect('dbus-signal', signal='PresencesChanged', args=[{amy_handle: (cs.PRESENCE_AWAY, 'away', 'At the pub')}]) stream.send(make_presence( '*****@*****.**', show='chat', status='I may have been drinking')) q.expect('dbus-signal', signal='PresencesChanged', args=[{amy_handle: (cs.PRESENCE_AVAILABLE, 'chat', 'I may have been drinking')}])
def test(q, bus, conn, stream): event = q.expect('stream-iq', query_ns=ns.ROSTER) amy_handle = conn.get_contact_handle_sync('*****@*****.**') event.stanza['type'] = 'result' item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'both' stream.send(event.stanza) stream.send(make_presence('*****@*****.**', show='away', status='At the pub')) q.expect('dbus-signal', signal='PresencesChanged', args=[{amy_handle: (cs.PRESENCE_AWAY, 'away', 'At the pub')}]) stream.send(make_presence( '*****@*****.**', show='chat', status='I may have been drinking')) e = q.expect('dbus-signal', signal='PresencesChanged', args=[{amy_handle: (cs.PRESENCE_AVAILABLE, 'chat', 'I may have been drinking')}]) amy_handle, asv = conn.Contacts.GetContactByID('*****@*****.**', [cs.CONN_IFACE_SIMPLE_PRESENCE]) assertEquals(e.args[0][amy_handle], asv.get(cs.ATTR_PRESENCE)) bob_handle, asv = conn.Contacts.GetContactByID('*****@*****.**', [cs.CONN_IFACE_SIMPLE_PRESENCE]) assertEquals((cs.PRESENCE_UNKNOWN, 'unknown', ''), asv.get(cs.ATTR_PRESENCE))
def send_presence(q, conn, stream, contact, caps, initial=True, show=None): h = conn.RequestHandles(cs.HT_CONTACT, [contact])[0] if initial: stream.send(make_presence(contact, status='hello')) q.expect_many( EventPattern('dbus-signal', signal='PresenceUpdate', args=[{ h: (0L, { u'available': { 'message': 'hello' } }) }]), EventPattern('dbus-signal', signal='PresencesChanged', args=[{ h: (2, u'available', 'hello') }])) # no special capabilities assertEquals([(h, cs.CHANNEL_TYPE_TEXT, 3, 0)], conn.Capabilities.GetCapabilities([h])) # send updated presence with caps info stream.send(make_presence(contact, show=show, status='hello', caps=caps)) return h
def test(q, bus, conn, stream): event = q.expect('stream-iq', query_ns=ns.ROSTER) amy, bob, che, dre, eve = conn.RequestHandles(cs.HT_CONTACT, ['*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**']) assertEquals({amy: UNKNOWN, bob: UNKNOWN, che: UNKNOWN, dre: UNKNOWN, eve: UNKNOWN, }, conn.SimplePresence.GetPresences([amy, bob, che, dre, eve])) # Before the server sends Gabble the roster, it relays an 'unavailable' # presence for one of the contacts we're subscribed to. This seems to # happen in practice when using Prosody with a shared roster: the presence # probes start coming back negatively before the shared roster is retrieved # and returned to the client. stream.send(make_presence('*****@*****.**', type='unavailable')) # Dre's presence is still unknown, since we don't have the roster. This # isn't a change per se---we checked above, and Dre's presence was # unknown---so it shouldn't be signalled. q.forbid_events([EventPattern('dbus-signal', signal='PresencesChanged', args=[{dre: UNKNOWN}])]) # We also receive an available presence from Eve before the roster arrives: # this presence should behave normally. stream.send(make_presence('*****@*****.**')) q.expect('dbus-signal', signal='PresencesChanged', args=[{eve: AVAILABLE}]) event.stanza['type'] = 'result' event.query.addChild(make_roster_item('*****@*****.**', 'both')) event.query.addChild(make_roster_item('*****@*****.**', 'from')) event.query.addChild(make_roster_item('*****@*****.**', 'to')) event.query.addChild(make_roster_item('*****@*****.**', 'both')) event.query.addChild(make_roster_item('*****@*****.**', 'both')) stream.send(event.stanza) # The presence for contacts on the roster whose subscription is 'to' or # 'both' but for whom we haven't already received presence should change # from 'unknown' (as checked above) to 'offline'. e = q.expect('dbus-signal', signal='PresencesChanged') changed_presences, = e.args assertEquals( {amy: OFFLINE, che: OFFLINE, dre: OFFLINE, }, changed_presences) assertEquals({amy: OFFLINE, bob: UNKNOWN, che: OFFLINE, dre: OFFLINE, eve: AVAILABLE, }, conn.SimplePresence.GetPresences([amy, bob, che, dre, eve]))
def test(q, bus, conn, stream): jids = ['*****@*****.**', '*****@*****.**', ] gregory, hawk = jids gregory_handle, hawk_handle = conn.get_contact_handles_sync(jids) event = q.expect('stream-iq', query_ns=ns.ROSTER) event.stanza['type'] = 'result' for jid in jids: item = event.query.addElement('item') item['jid'] = jid item['subscription'] = 'both' stream.send(event.stanza) q.expect('dbus-signal', signal='PresencesChanged', args=[{gregory_handle: (cs.PRESENCE_OFFLINE, 'offline', ''), hawk_handle: (cs.PRESENCE_OFFLINE, 'offline', ''), } ]) # Our server can't resolve unreachable.example.com so it sends us an error # presence for Gregory. (This is what Prosody actually does.) presence = make_presence(gregory, type='error') error_text = u'Connection failed: DNS resolution failed' presence.addChild( elem('error', type='cancel')( elem(ns.STANZA, 'remote-server-not-found'), elem(ns.STANZA, 'text')( error_text ) )) stream.send(presence) e = q.expect('dbus-signal', signal='PresencesChanged') presences, = e.args type_, status, message = presences[gregory_handle] assertEquals(cs.PRESENCE_ERROR, type_) assertEquals('error', status) assertEquals(error_text, message) # How about maybe the hawk's server is busted? presence = make_presence(hawk, type='error') presence.addChild( elem('error', type='cancel')( elem(ns.STANZA, 'internal-server-error'), )) stream.send(presence) e = q.expect('dbus-signal', signal='PresencesChanged') presences, = e.args type_, status, message = presences[hawk_handle] assertEquals(cs.PRESENCE_ERROR, type_) assertEquals('error', status) # FIXME: It might be less user-hostile to give some kind of readable # description of the error in future. assertEquals('internal-server-error', message)
def test(q, bus, conn, stream): self_presence = q.expect('stream-presence') c = xpath.queryForNodes('/presence/c', self_presence.stanza)[0] jid = '[email protected]/omg' # Gabble shouldn't send any disco requests to our contact during this test. q.forbid_events([ EventPattern('stream-iq', to=jid, iq_type='get', query_ns=ns.DISCO_INFO), ]) # Check that Gabble doesn't disco other clients with the same caps hash. p = make_presence(jid, caps={'node': c['node'], 'hash': c['hash'], 'ver': c['ver'], }) stream.send(p) sync_stream(q, stream) # Check that Gabble doesn't disco its own ext='' bundles (well, its own # bundles as advertised by Gabbles that don't do hashed caps) p = make_presence(jid, caps={'node': c['node'], 'ver': c['ver'], # omitting hash='' so Gabble doesn't ignore ext='' 'ext': 'voice-v1 video-v1', }) stream.send(p) sync_stream(q, stream) # Advertise some different capabilities, to change our own caps hash. add = [(cs.CHANNEL_TYPE_STREAMED_MEDIA, 2L**32-1), (cs.CHANNEL_TYPE_STREAM_TUBE, 2L**32-1), (cs.CHANNEL_TYPE_STREAM_TUBE, 2L**32-1)] remove = [] caps = conn.Capabilities.AdvertiseCapabilities(add, remove) self_presence = q.expect('stream-presence') c_ = xpath.queryForNodes('/presence/c', self_presence.stanza)[0] assertNotEquals(c['ver'], c_['ver']) # But then someone asks us for our old caps iq = IQ(stream, 'get') iq['from'] = jid query = iq.addElement((ns.DISCO_INFO, 'query')) query['node'] = c['node'] + '#' + c['ver'] stream.send(iq) # Gabble should still know what they are, and reply. This is actually quite # important: there's a bug in iChat where if you return an error to a disco # query, it just asks again, and again, and again... reply = q.expect('stream-iq', to=jid) assertEquals('result', reply.iq_type)
def test(q, bus, conn, stream): jids = [ '*****@*****.**', '*****@*****.**', ] gregory, hawk = jids gregory_handle, hawk_handle = conn.get_contact_handles_sync(jids) event = q.expect('stream-iq', query_ns=ns.ROSTER) event.stanza['type'] = 'result' for jid in jids: item = event.query.addElement('item') item['jid'] = jid item['subscription'] = 'both' stream.send(event.stanza) q.expect('dbus-signal', signal='PresencesChanged', args=[{ gregory_handle: (cs.PRESENCE_OFFLINE, 'offline', ''), hawk_handle: (cs.PRESENCE_OFFLINE, 'offline', ''), }]) # Our server can't resolve unreachable.example.com so it sends us an error # presence for Gregory. (This is what Prosody actually does.) presence = make_presence(gregory, type='error') error_text = u'Connection failed: DNS resolution failed' presence.addChild( elem('error', type='cancel')(elem(ns.STANZA, 'remote-server-not-found'), elem(ns.STANZA, 'text')(error_text))) stream.send(presence) e = q.expect('dbus-signal', signal='PresencesChanged') presences, = e.args type_, status, message = presences[gregory_handle] assertEquals(cs.PRESENCE_ERROR, type_) assertEquals('error', status) assertEquals(error_text, message) # How about maybe the hawk's server is busted? presence = make_presence(hawk, type='error') presence.addChild( elem('error', type='cancel')(elem(ns.STANZA, 'internal-server-error'), )) stream.send(presence) e = q.expect('dbus-signal', signal='PresencesChanged') presences, = e.args type_, status, message = presences[hawk_handle] assertEquals(cs.PRESENCE_ERROR, type_) assertEquals('error', status) # FIXME: It might be less user-hostile to give some kind of readable # description of the error in future. assertEquals('internal-server-error', message)
def test(q, bus, conn, stream, bytestream_cls, access_control): disco_event = q.expect('stream-iq', to='localhost', query_ns=ns.DISCO_ITEMS) announce_socks5_proxy(q, stream, disco_event.stanza) t.check_conn_properties(q, conn) self_handle = conn.Properties.Get(cs.CONN, "SelfHandle") alice_handle = conn.get_contact_handle_sync('alice@localhost') # send Alice's presence caps = { 'ext': '', 'ver': '0.0.0', 'node': 'http://example.com/fake-client0' } presence = make_presence('alice@localhost/Test', caps=caps) stream.send(presence) _, disco_event = q.expect_many( EventPattern('dbus-signal', signal='PresencesChanged', args = [{alice_handle: (2L, u'available', u'')}]), EventPattern('stream-iq', to='alice@localhost/Test', query_ns=ns.DISCO_INFO), ) # reply to disco query send_disco_reply(stream, disco_event.stanza, [], [ns.TUBES]) sync_stream(q, stream) offer_new_dbus_tube(q, bus, conn, stream, self_handle, alice_handle, bytestream_cls, access_control)
def test_39464(q, bus, conn, stream): """ Regression test for an issue where a form with no type='' attribute on the <x/> node would crash Gabble. """ client = 'fake:qutim' hash = 'blahblah' contact = '[email protected]/foo' caps = { 'node': client, 'ver': hash, 'hash': 'sha-1', } presence = make_presence(contact, status='hello', caps=caps) stream.send(presence) # Gabble looks up our capabilities event = q.expect('stream-iq', to=contact, query_ns=ns.DISCO_INFO) # Send a reply with a form without a type='' result = make_result_iq(stream, event.stanza, add_query_node=False) result.addChild( elem(ns.DISCO_INFO, 'query', node='%s#%s' % (client, hash))( # NB. no type='' attribute elem(ns.X_DATA, 'x') ) ) stream.send(result) # We don't really care what Gabble does, as long as it doesn't crash. sync_stream(q, stream)
def test(q, bus, conn, stream): contact = '[email protected]/delicious' presence = make_presence(contact, status='eat me!', caps={ 'node': 'oh:hai', 'ver': 'thar', }) thar_disco = EventPattern('stream-iq', to=contact, query_ns=ns.DISCO_INFO, query_node='oh:hai#thar') stream.send(presence) q.expect_many(thar_disco) # Okay, all good so far. But if we get the same caps node again from the # same contact, we shouldn't disco it again: we won't get any more trust # that way. This matters in practice, because Google's clients send a whole # bunch of presence stanzas in quick succession when they sign on. q.forbid_events([thar_disco]) stream.send(presence) sync_stream(q, stream) # If we get a presence update from this contact with some new ext='' # bundles, we should disco those, but not the nodes we're already querying. presence = make_presence(contact, status='eat me!', caps={ 'node': 'oh:hai', 'ver': 'thar', 'ext': 'good-sir', }) good_sir_disco = EventPattern('stream-iq', to=contact, query_ns=ns.DISCO_INFO, query_node='oh:hai#good-sir') stream.send(presence) q.expect_many(good_sir_disco) sync_stream(q, stream) # We should only disco ext='' attributes once per jid, too. q.forbid_events([good_sir_disco]) stream.send(presence) sync_stream(q, stream)
def send_presence(q, conn, stream, contact, caps, initial=True, show=None): h = conn.get_contact_handle_sync(contact) if initial: stream.send(make_presence(contact, status='hello')) q.expect('dbus-signal', signal='PresencesChanged', args=[{h: (2, u'available', 'hello')}]) # no special capabilities for rcc in get_contacts_capabilities_sync(conn, [h])[h]: assertEquals(cs.CHANNEL_TYPE_TEXT, rcc[0].get(cs.CHANNEL_TYPE)) # send updated presence with caps info stream.send(make_presence(contact, show=show, status='hello', caps=caps)) return h
def send_presence(q, conn, stream, contact, caps, initial=True, show=None): h = conn.RequestHandles(cs.HT_CONTACT, [contact])[0] if initial: stream.send(make_presence(contact, status='hello')) q.expect('dbus-signal', signal='PresencesChanged', args=[{h: (2, u'available', 'hello')}]) # no special capabilities assertEquals([(h, cs.CHANNEL_TYPE_TEXT, 3, 0)], conn.Capabilities.GetCapabilities([h])) # send updated presence with caps info stream.send(make_presence(contact, show=show, status='hello', caps=caps)) return h
def test(q, bus, conn, stream): conn.Connect() q.expect('dbus-signal', signal='StatusChanged', args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]) contact = '[email protected]/delicious' presence = make_presence(contact, type='available', status='eat me!', caps={ 'node': 'oh:hai', 'ver': 'thar', }) thar_disco = EventPattern('stream-iq', to=contact, query_ns=ns.DISCO_INFO, query_node='oh:hai#thar') stream.send(presence) q.expect_many(thar_disco) # Okay, all good so far. But if we get the same caps node again from the # same contact, we shouldn't disco it again: we won't get any more trust # that way. This matters in practice, because Google's clients send a whole # bunch of presence stanzas in quick succession when they sign on. q.forbid_events([thar_disco]) stream.send(presence) sync_stream(q, stream) # If we get a presence update from this contact with some new ext='' # bundles, we should disco those, but not the nodes we're already querying. presence = make_presence(contact, type='available', status='eat me!', caps={ 'node': 'oh:hai', 'ver': 'thar', 'ext': 'good-sir', }) good_sir_disco = EventPattern('stream-iq', to=contact, query_ns=ns.DISCO_INFO, query_node='oh:hai#good-sir') stream.send(presence) q.expect_many(good_sir_disco) sync_stream(q, stream) # We should only disco ext='' attributes once per jid, too. q.forbid_events([good_sir_disco]) stream.send(presence) sync_stream(q, stream)
def send_presence(q, stream, contact_jid, identity): ver = compute_caps_hash([identity], features, {}) stream.send( make_presence(contact_jid, status='Hello', caps={ 'node': client, 'hash': 'sha-1', 'ver': ver }))
def receive_caps(q, conn, stream, contact, contact_handle, features, expected_caps, expect_disco=True, expect_ccc=True): presence = make_presence(contact, status='hello') c = presence.addElement((ns.CAPS, 'c')) c['node'] = client c['ver'] = compute_caps_hash([], features if features is not None else [], {}) c['hash'] = 'sha-1' stream.send(presence) if expect_disco: # Gabble looks up our capabilities event = q.expect('stream-iq', to=contact, query_ns=ns.DISCO_INFO) query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + c['ver'] # send good reply result = make_result_iq(stream, event.stanza) query = result.firstChildElement() query['node'] = client + '#' + c['ver'] for f in features: feature = query.addElement('feature') feature['var'] = f stream.send(result) if expect_ccc: event = q.expect('dbus-signal', signal='ContactCapabilitiesChanged') announced_ccs, = event.args assertSameElements(expected_caps, announced_ccs) else: # Make sure Gabble's got the caps sync_stream(q, stream) caps = get_contacts_capabilities_sync(conn, [contact_handle]) assertSameElements(expected_caps, caps) # test again, to check GetContactCapabilities does not have side effect caps = get_contacts_capabilities_sync(conn, [contact_handle]) assertSameElements(expected_caps, caps) # check the Contacts interface give the same caps caps_via_contacts_iface = conn.Contacts.GetContactAttributes( [contact_handle], [cs.CONN_IFACE_CONTACT_CAPS], False) \ [contact_handle][cs.ATTR_CONTACT_CAPABILITIES] assertSameElements(caps[contact_handle], caps_via_contacts_iface)
def test2(q, bus, conn, stream): marco_pidgin = '[email protected]/Pidgin' marco_phone = '[email protected]/N900' handle = conn.get_contact_handle_sync(marco_pidgin) # pidgin comes online contact_online(q, conn, stream, marco_pidgin, PC) types = get_client_types(conn, handle) assertSameSets(['pc'], types) # phone comes online contact_online(q, conn, stream, marco_phone, PHONE, initial=False) types = get_client_types(conn, handle) assertSameSets(['pc'], types) sync_stream(q, stream) # pidgin goes offline stream.send(make_presence(marco_pidgin, type='unavailable')) # no presence signal q.expect('dbus-signal', signal='ClientTypesUpdated', args=[handle, ['phone']]) # pidgin comes back online caps, _, _ = build_stuff(PC) stream.send(make_presence(marco_pidgin, status='hello', caps=caps)) q.expect('dbus-signal', signal='ClientTypesUpdated', args=[handle, ['pc']]) attrs = conn.Contacts.GetContactAttributes([handle], [cs.CONN_IFACE_CLIENT_TYPES], False) assertContains(handle, attrs) attr = cs.CONN_IFACE_CLIENT_TYPES + '/client-types' assertContains(attr, attrs[handle]) assertEquals(['pc'], attrs[handle][attr])
def test(q, bus, conn, stream): event = q.expect('stream-iq', query_ns=ns.ROSTER) amy_handle = conn.get_contact_handle_sync('*****@*****.**') event.stanza['type'] = 'result' item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'both' stream.send(event.stanza) stream.send(make_presence('*****@*****.**', show='away', status='At the pub')) q.expect('dbus-signal', signal='PresencesChanged', args=[{ amy_handle: (cs.PRESENCE_AWAY, 'away', 'At the pub') }]) stream.send( make_presence('*****@*****.**', show='chat', status='I may have been drinking')) e = q.expect('dbus-signal', signal='PresencesChanged', args=[{ amy_handle: (cs.PRESENCE_AVAILABLE, 'chat', 'I may have been drinking') }]) amy_handle, asv = conn.Contacts.GetContactByID( '*****@*****.**', [cs.CONN_IFACE_SIMPLE_PRESENCE]) assertEquals(e.args[0][amy_handle], asv.get(cs.ATTR_PRESENCE)) bob_handle, asv = conn.Contacts.GetContactByID( '*****@*****.**', [cs.CONN_IFACE_SIMPLE_PRESENCE]) assertEquals((cs.PRESENCE_UNKNOWN, 'unknown', ''), asv.get(cs.ATTR_PRESENCE))
def test_success(q, gateways_iface, stream): call_async(q, gateways_iface, 'Register', 'talkd.example.com', '1970', 's3kr1t') e = q.expect('stream-iq', iq_type='set', query_name='query', query_ns=ns.REGISTER, to='talkd.example.com') assertEquals('1970', xpath.queryForString('/query/username', e.query)) assertEquals('s3kr1t', xpath.queryForString('/query/password', e.query)) acknowledge_iq(stream, e.stanza) q.expect_many( EventPattern('dbus-return', method='Register'), EventPattern('stream-presence', presence_type='subscribe', to='talkd.example.com'), ) stream.send(make_presence('talkd.example.com', type='subscribed'))
def worker(q, bus, conn, stream, should_decloak): decloak_automatically = conn.Get(cs.CONN_IFACE_GABBLE_DECLOAK, 'DecloakAutomatically', dbus_interface=cs.PROPERTIES_IFACE) assertEquals(should_decloak, decloak_automatically) amy_handle = conn.get_contact_handle_sync('*****@*****.**') # Amy directs presence to us presence = make_presence('[email protected]/panopticon') decloak = presence.addElement((ns.TEMPPRES, 'temppres')) decloak['reason'] = 'media' stream.send(presence) events = [ EventPattern('dbus-signal', signal='PresencesChanged', args=[{amy_handle: (cs.PRESENCE_AVAILABLE, 'available', '')}]), EventPattern('dbus-signal', signal='DecloakRequested', args=[amy_handle, 'media', should_decloak]), ] forbidden = [] if should_decloak: events.append(EventPattern('stream-presence', to='[email protected]/panopticon')) else: forbidden = [EventPattern('stream-presence')] q.forbid_events(forbidden) q.expect_many(*events) presence = make_presence('[email protected]/panopticon', type='unavailable') stream.send(presence) q.expect('dbus-signal', signal='PresencesChanged', args=[{amy_handle: (cs.PRESENCE_OFFLINE, 'offline', '')}]) q.unforbid_events(forbidden)
def receive_caps(q, conn, stream, contact, contact_handle, features, expected_caps, expect_disco=True, expect_ccc=True): presence = make_presence(contact, status='hello') c = presence.addElement((ns.CAPS, 'c')) c['node'] = client c['ver'] = compute_caps_hash([], features if features is not None else [], {}) c['hash'] = 'sha-1' stream.send(presence) if expect_disco: # Gabble looks up our capabilities event = q.expect('stream-iq', to=contact, query_ns=ns.DISCO_INFO) query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + c['ver'] # send good reply result = make_result_iq(stream, event.stanza) query = result.firstChildElement() query['node'] = client + '#' + c['ver'] for f in features: feature = query.addElement('feature') feature['var'] = f stream.send(result) if expect_ccc: event = q.expect('dbus-signal', signal='ContactCapabilitiesChanged') announced_ccs, = event.args assertSameElements(expected_caps, announced_ccs) else: # Make sure Gabble's got the caps sync_stream(q, stream) caps = conn.ContactCapabilities.GetContactCapabilities([contact_handle]) assertSameElements(expected_caps, caps) # test again, to check GetContactCapabilities does not have side effect caps = conn.ContactCapabilities.GetContactCapabilities([contact_handle]) assertSameElements(expected_caps, caps) # check the Contacts interface give the same caps caps_via_contacts_iface = conn.Contacts.GetContactAttributes( [contact_handle], [cs.CONN_IFACE_CONTACT_CAPS], False) \ [contact_handle][cs.ATTR_CONTACT_CAPABILITIES] assertSameElements(caps[contact_handle], caps_via_contacts_iface)
def connect_and_announce_alice(q, bus, conn, stream): q.forbid_events(proxy_query_events) # Send Alice's presence caps = { 'ext': '', 'ver': '0.0.0', 'node': 'http://example.com/fake-client0' } presence = make_presence('alice@localhost/Test', caps=caps) stream.send(presence) disco_event = q.expect('stream-iq', to='alice@localhost/Test', query_ns=ns.DISCO_INFO) send_disco_reply( stream, disco_event.stanza, [], [ns.TUBES, ns.FILE_TRANSFER]) sync_stream(q, stream) q.unforbid_events(proxy_query_events)
def connect_and_announce_alice(q, bus, conn, stream): q.forbid_events(proxy_query_events) # Send Alice's presence caps = { 'ext': '', 'ver': '0.0.0', 'node': 'http://example.com/fake-client0' } presence = make_presence('alice@localhost/Test', caps=caps) stream.send(presence) disco_event = q.expect('stream-iq', to='alice@localhost/Test', query_ns=ns.DISCO_INFO) send_disco_reply(stream, disco_event.stanza, [], [ns.TUBES, ns.FILE_TRANSFER]) sync_stream(q, stream) q.unforbid_events(proxy_query_events)
def test(q, bus, conn, stream, bytestream_cls, access_control): disco_event = q.expect('stream-iq', to='localhost', query_ns=ns.DISCO_ITEMS) announce_socks5_proxy(q, stream, disco_event.stanza) t.check_conn_properties(q, conn) self_handle = conn.Properties.Get(cs.CONN, "SelfHandle") alice_handle = conn.get_contact_handle_sync('alice@localhost') # send Alice's presence caps = { 'ext': '', 'ver': '0.0.0', 'node': 'http://example.com/fake-client0' } presence = make_presence('alice@localhost/Test', caps=caps) stream.send(presence) _, disco_event = q.expect_many( EventPattern('dbus-signal', signal='PresencesChanged', args=[{ alice_handle: (2L, u'available', u'') }]), EventPattern('stream-iq', to='alice@localhost/Test', query_ns=ns.DISCO_INFO), ) # reply to disco query send_disco_reply(stream, disco_event.stanza, [], [ns.TUBES]) sync_stream(q, stream) offer_new_dbus_tube(q, bus, conn, stream, self_handle, alice_handle, bytestream_cls, access_control)
def test(q, bus, conn, stream): conn.Connect() q.expect('dbus-signal', signal='StatusChanged', args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]) self_handle = conn.GetSelfHandle() jid = '*****@*****.**' foo_handle = conn.RequestHandles(cs.HT_CONTACT, [jid])[0] path = conn.Requests.CreateChannel( { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, cs.TARGET_HANDLE: foo_handle, })[0] chan = wrap_channel(bus.get_object(conn.bus_name, path), 'Text', ['ChatState', 'Destroyable']) presence = make_presence('[email protected]/Foo', status='hello', caps={ 'node': 'http://telepathy.freedesktop.org/homeopathy', 'ver' : '0.1', }) stream.send(presence) version_event = q.expect('stream-iq', to='[email protected]/Foo', query_ns='http://jabber.org/protocol/disco#info', query_node='http://telepathy.freedesktop.org/homeopathy#0.1') result = make_result_iq(stream, version_event.stanza) query = result.firstChildElement() feature = query.addElement('feature') feature['var'] = 'http://jabber.org/protocol/chatstates' stream.send(result) sync_stream(q, stream) # Receiving chat states: # Composing... m = domish.Element((None, 'message')) m['from'] = '[email protected]/Foo' m['type'] = 'chat' m.addElement((ns.CHAT_STATES, 'composing')) stream.send(m) changed = q.expect('dbus-signal', signal='ChatStateChanged') handle, state = changed.args assertEquals(foo_handle, handle) assertEquals(cs.CHAT_STATE_COMPOSING, state) # Message! m = domish.Element((None, 'message')) m['from'] = '[email protected]/Foo' m['type'] = 'chat' m.addElement((ns.CHAT_STATES, 'active')) m.addElement('body', content='hello') stream.send(m) changed = q.expect('dbus-signal', signal='ChatStateChanged') handle, state = changed.args assertEquals(foo_handle, handle) assertEquals(cs.CHAT_STATE_ACTIVE, state) # Sending chat states: # Composing... call_async(q, chan.ChatState, 'SetChatState', cs.CHAT_STATE_COMPOSING) stream_message = q.expect('stream-message') check_state_notification(stream_message.stanza, 'composing') # XEP 0085: # every content message SHOULD contain an <active/> notification. call_async(q, chan.Text, 'Send', 0, 'hi.') stream_message = q.expect('stream-message') elem = stream_message.stanza assert elem.name == 'message' assert elem['type'] == 'chat', elem['type'] def is_body(e): if e.name == 'body': assert e.children[0] == u'hi.', e.toXml() return True return False def is_active(e): if e.uri == ns.CHAT_STATES: assert e.name == 'active', e.toXml() return True return False children = list(elem.elements()) assert len(filter(is_body, children)) == 1, elem.toXml() assert len(filter(is_active, children)) == 1, elem.toXml() # Close the channel without acking the received message. The peer should # get a <gone/> notification, and the channel should respawn. chan.Close() gone, _, _ = q.expect_many( EventPattern('stream-message'), EventPattern('dbus-signal', signal='Closed'), EventPattern('dbus-signal', signal='NewChannel'), ) check_state_notification(gone.stanza, 'gone') # Reusing the proxy object because we happen to know it'll be at the same # path... # Destroy the channel. The peer shouldn't get a <gone/> notification, since # we already said we were gone and haven't sent them any messages to the # contrary. es = [EventPattern('stream-message')] q.forbid_events(es) chan.Destroyable.Destroy() sync_stream(q, stream) # Make the channel anew. path = conn.Requests.CreateChannel( { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, cs.TARGET_HANDLE: foo_handle, })[0] chan = wrap_channel(bus.get_object(conn.bus_name, path), 'Text', ['ChatState', 'Destroyable']) # Close it immediately; the peer should again not get a <gone/> # notification, since we haven't sent any notifications on that channel. chan.Close() sync_stream(q, stream)
def test(q, bus, conn, stream): event = q.expect('stream-iq', query_ns=ns.ROSTER) amy, bob, che, dre, eve = conn.get_contact_handles_sync( ['*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**']) assertEquals({amy: UNKNOWN, bob: UNKNOWN, che: UNKNOWN, dre: UNKNOWN, eve: UNKNOWN, }, get_contacts_presences_sync(conn, [amy, bob, che, dre, eve])) # Before the server sends Gabble the roster, it relays an 'unavailable' # presence for one of the contacts we're subscribed to. This seems to # happen in practice when using Prosody with a shared roster: the presence # probes start coming back negatively before the shared roster is retrieved # and returned to the client. stream.send(make_presence('*****@*****.**', type='unavailable')) # Dre's presence is still unknown, since we don't have the roster. This # isn't a change per se---we checked above, and Dre's presence was # unknown---so it shouldn't be signalled. q.forbid_events([EventPattern('dbus-signal', signal='PresencesChanged', args=[{dre: UNKNOWN}])]) # We also receive an available presence from Eve before the roster arrives: # this presence should behave normally. stream.send(make_presence('*****@*****.**')) q.expect('dbus-signal', signal='PresencesChanged', args=[{eve: AVAILABLE}]) # We also get a message from a contact before we get the roster (presumably # they sent this while we were offline?). This shouldn't affect the contact # being reported as offline when we finally do get the roster, but it used # to: <https://bugs.freedesktop.org/show_bug.cgi?id=41743>. stream.send( elem('message', from_='*****@*****.**', type='chat')( elem('body')(u'why are you never online?') )) q.expect('dbus-signal', signal='MessageReceived') event.stanza['type'] = 'result' event.query.addChild(make_roster_item('*****@*****.**', 'both')) event.query.addChild(make_roster_item('*****@*****.**', 'from')) event.query.addChild(make_roster_item('*****@*****.**', 'to')) event.query.addChild(make_roster_item('*****@*****.**', 'both')) event.query.addChild(make_roster_item('*****@*****.**', 'both')) stream.send(event.stanza) # The presence for contacts on the roster whose subscription is 'to' or # 'both' but for whom we haven't already received presence should change # from 'unknown' (as checked above) to 'offline'. e = q.expect('dbus-signal', signal='PresencesChanged') changed_presences, = e.args assertEquals( {amy: OFFLINE, che: OFFLINE, dre: OFFLINE, }, changed_presences) assertEquals({amy: OFFLINE, bob: UNKNOWN, che: OFFLINE, dre: OFFLINE, eve: AVAILABLE, }, get_contacts_presences_sync(conn, [amy, bob, che, dre, eve]))
def send_presence(q, stream, contact_jid, identity): ver = compute_caps_hash([identity], features, {}) stream.send(make_presence(contact_jid, status='Hello', caps={'node': client, 'hash': 'sha-1', 'ver': ver}))
def test(q, bus, conn, stream): # check all these types appear as they should contact_online(q, conn, stream, '[email protected]/lol', BOT) contact_online(q, conn, stream, '[email protected]/lol', CONSOLE) contact_online(q, conn, stream, '[email protected]/lol', GAME) contact_online(q, conn, stream, '[email protected]/lol', HANDHELD) contact_online(q, conn, stream, '[email protected]/lol', PC) contact_online(q, conn, stream, '[email protected]/lol', PHONE) contact_online(q, conn, stream, '[email protected]/lol', WEB) contact_online(q, conn, stream, '[email protected]/lol', SMS) meredith_one = '[email protected]/One' meredith_two = '[email protected]/Two' meredith_three = '[email protected]/Three' meredith_handle = conn.get_contact_handle_sync(meredith_one) # Meredith signs in from one resource contact_online(q, conn, stream, meredith_one, PC, show='chat') # * One: chat: pc # ClientTypes should be: ['pc'] # Meredith signs in from another resource contact_online(q, conn, stream, meredith_two, PHONE, show='dnd', initial=False) # * One: chat: pc # * Two: dnd: phone # ClientTypes should be: ['pc'] # check we're still a PC types = get_client_types(conn, meredith_handle) assertLength(1, types) assertEquals('pc', types[0]) types = conn.RequestClientTypes(meredith_handle, dbus_interface=cs.CONN_IFACE_CLIENT_TYPES) assertLength(1, types) assertEquals('pc', types[0]) # Two now becomes more available stream.send(make_presence(meredith_two, show='chat')) # * One: chat: pc # * Two: chat: phone # ClientTypes should be: ['pc'] types = get_client_types(conn, meredith_handle) assertEquals('pc', types[0]) # One now becomes less available stream.send(make_presence(meredith_one, show='away')) # * One: away: pc # * Two: chat: phone # ClientTypes should be: ['phone'] # wait for the presence change q.expect('dbus-signal', signal='PresencesChanged', args=[{ meredith_handle: (cs.PRESENCE_AVAILABLE, 'chat', '') }]) # now wait for the change in client type event = q.expect('dbus-signal', signal='ClientTypesUpdated') assertEquals([meredith_handle, ['phone']], event.args) # make One more available again stream.send(make_presence(meredith_one, show='chat', status='lawl')) # * One: chat: pc # * Two: chat: phone # ClientTypes should be: ['pc'] # wait for the presence change q.expect('dbus-signal', signal='PresencesChanged', args=[{ meredith_handle: (cs.PRESENCE_AVAILABLE, 'chat', 'lawl') }]) # now wait for the change in client type event = q.expect('dbus-signal', signal='ClientTypesUpdated') assertEquals([meredith_handle, ['pc']], event.args) # both One and Two go away stream.send(make_presence(meredith_one, show='away')) # * One: away: pc # * Two: chat: phone # ClientTypes should be: ['phone'] stream.send(make_presence(meredith_two, show='away')) # * One: away: pc # * Two: away: phone # ClientTypes should be: ['pc'] # wait for the presence change q.expect('dbus-signal', signal='PresencesChanged', args=[{ meredith_handle: (cs.PRESENCE_AWAY, 'away', '') }]) # check it still thinks we're a PC types = get_client_types(conn, meredith_handle) assertEquals('pc', types[0]) # Three, with multiple identities, signs in identities = [PHONE[0], CONSOLE[0], HANDHELD[0], BOT[0]] contact_online(q, conn, stream, meredith_three, identities, show='chat', initial=False) # * One: away: pc # * Two: away: phone # * Three: chat: phone, console, handheld, bot # ClientTypes should be: ['phone', 'console', 'handheld', 'bot'] in some order # wait for the presence change q.expect('dbus-signal', signal='PresencesChanged', args=[{ meredith_handle: (cs.PRESENCE_AVAILABLE, 'chat', 'hello') }]) # now wait for the change in client type event = q.expect('dbus-signal', signal='ClientTypesUpdated') assertEquals(meredith_handle, event.args[0]) assertEquals(['bot', 'console', 'handheld', 'phone'], sorted(event.args[1])) # that'll do # # ... # # wait wait! no it won't! Here's a regression test for # <https://bugs.freedesktop.org/show_bug.cgi?id=31772>. (caps, client, types) = build_stuff(TRANSIENT_PHONE) contact = '[email protected]/hai' send_presence(q, conn, stream, contact, caps) stanza = expect_disco(q, contact, client, caps) stream.send(make_presence(contact, type='unavailable')) send_disco_reply(stream, stanza, TRANSIENT_PHONE, []) # Gabble used to crash upon receiving a disco reply from a contact who's no # longer in the presence cache. So we sync here to check if it's died. sync_stream(q, stream)
def test(q, bus, conn, stream): event = q.expect('stream-iq', query_ns=ns.ROSTER) amy, bob, che, dre, eve = conn.get_contact_handles_sync([ '*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**' ]) assertEquals( { amy: UNKNOWN, bob: UNKNOWN, che: UNKNOWN, dre: UNKNOWN, eve: UNKNOWN, }, get_contacts_presences_sync(conn, [amy, bob, che, dre, eve])) # Before the server sends Gabble the roster, it relays an 'unavailable' # presence for one of the contacts we're subscribed to. This seems to # happen in practice when using Prosody with a shared roster: the presence # probes start coming back negatively before the shared roster is retrieved # and returned to the client. stream.send(make_presence('*****@*****.**', type='unavailable')) # Dre's presence is still unknown, since we don't have the roster. This # isn't a change per se---we checked above, and Dre's presence was # unknown---so it shouldn't be signalled. q.forbid_events([ EventPattern('dbus-signal', signal='PresencesChanged', args=[{ dre: UNKNOWN }]) ]) # We also receive an available presence from Eve before the roster arrives: # this presence should behave normally. stream.send(make_presence('*****@*****.**')) q.expect('dbus-signal', signal='PresencesChanged', args=[{eve: AVAILABLE}]) # We also get a message from a contact before we get the roster (presumably # they sent this while we were offline?). This shouldn't affect the contact # being reported as offline when we finally do get the roster, but it used # to: <https://bugs.freedesktop.org/show_bug.cgi?id=41743>. stream.send( elem('message', from_='*****@*****.**', type='chat')(elem('body')(u'why are you never online?'))) q.expect('dbus-signal', signal='MessageReceived') event.stanza['type'] = 'result' event.query.addChild(make_roster_item('*****@*****.**', 'both')) event.query.addChild(make_roster_item('*****@*****.**', 'from')) event.query.addChild(make_roster_item('*****@*****.**', 'to')) event.query.addChild(make_roster_item('*****@*****.**', 'both')) event.query.addChild(make_roster_item('*****@*****.**', 'both')) stream.send(event.stanza) # The presence for contacts on the roster whose subscription is 'to' or # 'both' but for whom we haven't already received presence should change # from 'unknown' (as checked above) to 'offline'. e = q.expect('dbus-signal', signal='PresencesChanged') changed_presences, = e.args assertEquals({ amy: OFFLINE, che: OFFLINE, dre: OFFLINE, }, changed_presences) assertEquals( { amy: OFFLINE, bob: UNKNOWN, che: OFFLINE, dre: OFFLINE, eve: AVAILABLE, }, get_contacts_presences_sync(conn, [amy, bob, che, dre, eve]))
def send_remote_presence(self): presence = make_presence(self.remote_jid, self.local_jid, caps=self.remote_caps) self.stream.send(presence.toXml())
def test_two_clients(q, bus, conn, stream, contact1, contact2, contact_handle1, contact_handle2, client, broken_hash): global caps_changed_flag presence = make_presence(contact1, status='hello') stream.send(presence) event = q.expect_many( EventPattern('dbus-signal', signal='PresenceUpdate', args=[{contact_handle1: (0L, {u'available': {'message': 'hello'}})}]), EventPattern('dbus-signal', signal='PresencesChanged', args=[{contact_handle1: (2, u'available', 'hello')}])) presence = make_presence(contact2, status='hello') stream.send(presence) event = q.expect_many( EventPattern('dbus-signal', signal='PresenceUpdate', args=[{contact_handle2: (0L, {u'available': {'message': 'hello'}})}]), EventPattern('dbus-signal', signal='PresencesChanged', args=[{contact_handle2: (2, u'available', 'hello')}])) # no special capabilities basic_caps = [(contact_handle1, cs.CHANNEL_TYPE_TEXT, 3, 0)] assert conn.Capabilities.GetCapabilities([contact_handle1]) == basic_caps basic_caps = [(contact_handle2, cs.CHANNEL_TYPE_TEXT, 3, 0)] assert conn.Capabilities.GetCapabilities([contact_handle2]) == basic_caps # send updated presence with Jingle caps info ver = compute_caps_hash([], jingle_av_features, {}) caps = { 'node': client, 'ver': ver, 'hash': 'sha-1', } presence = make_presence(contact1, status='hello', caps=caps) stream.send(presence) presence = make_presence(contact2, status='hello', caps=caps) stream.send(presence) # Gabble looks up our capabilities event = q.expect('stream-iq', to=contact1, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + ver # don't receive any D-Bus signal sync_dbus(bus, q, conn) assert caps_changed_flag == False result = make_caps_disco_reply(stream, event.stanza, jingle_av_features) if broken_hash: # make the hash break! query = result.firstChildElement() query.addElement('feature')['var'] = 'http://example.com/another-feature' stream.send(result) if broken_hash: # Gabble looks up our capabilities again because the first contact # failed to provide a valid hash event = q.expect('stream-iq', to=contact2, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + ver # don't receive any D-Bus signal sync_dbus(bus, q, conn) assert caps_changed_flag == False # send good reply result = make_caps_disco_reply(stream, event.stanza, jingle_av_features) stream.send(result) # we can now do audio calls with both contacts event = q.expect('dbus-signal', signal='CapabilitiesChanged', args=[[(contact_handle2, cs.CHANNEL_TYPE_STREAMED_MEDIA, 0, 3, 0, cs.MEDIA_CAP_AUDIO | cs.MEDIA_CAP_VIDEO)]]) if not broken_hash: # if the first contact failed to provide a good hash, it does not # deserve its capabilities to be understood by Gabble! event = q.expect('dbus-signal', signal='CapabilitiesChanged', args=[[(contact_handle1, cs.CHANNEL_TYPE_STREAMED_MEDIA, 0, 3, 0, cs.MEDIA_CAP_AUDIO | cs.MEDIA_CAP_VIDEO)]]) caps_changed_flag = False # don't receive any D-Bus signal sync_dbus(bus, q, conn) assert caps_changed_flag == False
def test_ft_caps_from_contact(q, bus, conn, stream, contact, contact_handle, client): conn_caps_iface = dbus.Interface(conn, cs.CONN_IFACE_CONTACT_CAPS) conn_contacts_iface = dbus.Interface(conn, cs.CONN_IFACE_CONTACTS) # send presence with no FT cap presence = make_presence(contact, status='hello') c = presence.addElement((ns.CAPS, 'c')) c['node'] = client c['ver'] = compute_caps_hash([], [], {}) c['hash'] = 'sha-1' stream.send(presence) # Gabble looks up our capabilities event = q.expect('stream-iq', to=contact, query_ns=ns.DISCO_INFO) query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + c['ver'] # send good reply result = make_result_iq(stream, event.stanza) query = result.firstChildElement() query['node'] = client + '#' + c['ver'] stream.send(result) # no change in ContactCapabilities, so no signal ContactCapabilitiesChanged sync_stream(q, stream) # no special capabilities basic_caps = dbus.Dictionary( {contact_handle: [(text_fixed_properties, text_allowed_properties)]}) caps = get_contacts_capabilities_sync(conn, [contact_handle]) assert caps == basic_caps, caps # test again, to check GetContactCapabilities does not have side effect caps = get_contacts_capabilities_sync(conn, [contact_handle]) assert caps == basic_caps, caps # check the Contacts interface give the same caps caps_via_contacts_iface = conn_contacts_iface.GetContactAttributes( [contact_handle], [cs.CONN_IFACE_CONTACT_CAPS], False) \ [contact_handle][cs.ATTR_CONTACT_CAPABILITIES] assert caps_via_contacts_iface == caps[contact_handle], \ caps_via_contacts_iface # send presence with ft capa presence = make_presence(contact, status='hello') c = presence.addElement((ns.CAPS, 'c')) c['node'] = client c['ver'] = compute_caps_hash([], [ns.FILE_TRANSFER], {}) c['hash'] = 'sha-1' stream.send(presence) # Gabble looks up our capabilities event = q.expect('stream-iq', to=contact, query_ns=ns.DISCO_INFO) query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + c['ver'] # send good reply result = make_result_iq(stream, event.stanza) query = result.firstChildElement() query['node'] = client + '#' + c['ver'] feature = query.addElement('feature') feature['var'] = ns.FILE_TRANSFER stream.send(result) generic_tubes_caps = dbus.Dictionary({ contact_handle: [(text_fixed_properties, text_allowed_properties), (ft_fixed_properties, ft_allowed_properties)] }) event = q.expect('dbus-signal', signal='ContactCapabilitiesChanged') assert len(event.args) == 1 assert event.args[0] == generic_tubes_caps caps = get_contacts_capabilities_sync(conn, [contact_handle]) assert caps == generic_tubes_caps, caps # test again, to check GetContactCapabilities does not have side effect caps = get_contacts_capabilities_sync(conn, [contact_handle]) assert caps == generic_tubes_caps, caps # check the Contacts interface give the same caps caps_via_contacts_iface = conn_contacts_iface.GetContactAttributes( [contact_handle], [cs.CONN_IFACE_CONTACT_CAPS], False) \ [contact_handle][cs.ATTR_CONTACT_CAPABILITIES] assert caps_via_contacts_iface == caps[contact_handle], \ caps_via_contacts_iface
def test(q, bus, conn, stream): self_presence = q.expect('stream-presence') c = xpath.queryForNodes('/presence/c', self_presence.stanza)[0] jid = '[email protected]/omg' # Gabble shouldn't send any disco requests to our contact during this test. q.forbid_events([ EventPattern('stream-iq', to=jid, iq_type='get', query_ns=ns.DISCO_INFO), ]) # Check that Gabble doesn't disco other clients with the same caps hash. p = make_presence(jid, caps={ 'node': c['node'], 'hash': c['hash'], 'ver': c['ver'], }) stream.send(p) sync_stream(q, stream) # Check that Gabble doesn't disco its own ext='' bundles (well, its own # bundles as advertised by Gabbles that don't do hashed caps) p = make_presence( jid, caps={ 'node': c['node'], 'ver': c['ver'], # omitting hash='' so Gabble doesn't ignore ext='' 'ext': 'voice-v1 video-v1', }) stream.send(p) sync_stream(q, stream) conn.ContactCapabilities.UpdateCapabilities([ (cs.CLIENT + '.AbiWord', [ { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_STREAM_TUBE, cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, cs.STREAM_TUBE_SERVICE: 'x-abiword' }, { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_STREAM_TUBE, cs.TARGET_HANDLE_TYPE: cs.HT_ROOM, cs.STREAM_TUBE_SERVICE: 'x-abiword' }, ], []), (cs.CLIENT + '.KCall', [ { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CALL }, { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CALL, cs.CALL_INITIAL_AUDIO: True }, { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CALL, cs.CALL_INITIAL_VIDEO: True }, ], [ cs.CHANNEL_TYPE_CALL + '/gtalk-p2p', cs.CHANNEL_TYPE_CALL + '/ice-udp', cs.CHANNEL_TYPE_CALL + '/video/h264', ]), ]) self_presence = q.expect('stream-presence') c_ = xpath.queryForNodes('/presence/c', self_presence.stanza)[0] assertNotEquals(c['ver'], c_['ver']) for suffix in [ c['ver'], 'voice-v1', 'video-v1', 'camera-v1', 'share-v1', 'pmuc-v1' ] + list(c_['ext'].split()): # But then someone asks us for our old caps iq = IQ(stream, 'get') iq['from'] = jid query = iq.addElement((ns.DISCO_INFO, 'query')) query['node'] = c['node'] + '#' + suffix stream.send(iq) # Gabble should still know what they are, and reply. This is # actually quite important: there's a bug in iChat where if you # return an error to a disco query, it just asks again, and again, # and again... reply = q.expect('stream-iq', to=jid) assertEquals('result', reply.iq_type)
def test(q, bus, conn, stream): caps = { 'node': client, 'ver': '0.1', } update_contact_caps(q, conn, stream, '[email protected]/Foo', caps) update_contact_caps(q, conn, stream, '[email protected]/Foo', caps) # Meredith signs in from one resource. update_contact_caps(q, conn, stream, '[email protected]/One', caps) # Meredith signs in from another resource with the same client. We don't # need to disco her, even though we don't trust this caps node in general # yet, because she's already told us what it means. meredith_two = '[email protected]/Two' q.forbid_events([ EventPattern('stream-iq', to=meredith_two, query_ns=ns.DISCO_INFO) ]) stream.send(make_presence(meredith_two, 'hello', caps=caps)) sync_stream(q, stream) # Jens signs in from one resource, which is slow to answer the disco query. jens_one = '[email protected]/One' j = send_presence(q, conn, stream, jens_one, caps) j_stanza = expect_disco(q, jens_one, client, caps) # Jens now signs in elsewhere with the same client; we disco it (maybe # it'll reply sooner? Maybe his first client's network connection went away # and the server hasn't noticed yet?) and it replies immediately. update_contact_caps (q, conn, stream, '[email protected]/Two', caps, initial=False) # Jens' first client replies. We don't expect any caps changes here, and # this shouldn't count as a second point towards the five we need to trust # this caps node. send_disco_reply(stream, j_stanza, [], features) check_caps (conn, j) update_contact_caps (q, conn, stream, '[email protected]/Foo', caps) # Now five distinct contacts have told us what this caps node means, we # trust it. update_contact_caps (q, conn, stream, '[email protected]/Foo', caps, disco = False) update_contact_caps (q, conn, stream, '[email protected]/Foo', caps, disco = False) caps = { 'node': client, 'ver': compute_caps_hash([], features, fake_client_dataforms), 'hash': 'sha-1', } update_contact_caps(q, conn, stream, '[email protected]/Foo', caps, dataforms = fake_client_dataforms) # We can verify the reply for these caps against the hash, and thus never # need to disco it again. update_contact_caps(q, conn, stream, '[email protected]/Foo', caps, disco = False, dataforms = fake_client_dataforms) update_contact_caps(q, conn, stream, '[email protected]/Foo', caps, disco = False, dataforms = fake_client_dataforms)
def test(q, bus, conn, stream): self_handle = conn.GetSelfHandle() jid = '*****@*****.**' full_jid = '[email protected]/Foo' foo_handle = conn.RequestHandles(cs.HT_CONTACT, [jid])[0] path = conn.Requests.CreateChannel( { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, cs.TARGET_HANDLE: foo_handle, })[0] chan = wrap_channel(bus.get_object(conn.bus_name, path), 'Text', ['ChatState', 'Destroyable']) presence = make_presence(full_jid, status='hello', caps={ 'node': 'http://telepathy.freedesktop.org/homeopathy', 'ver' : '0.1', }) stream.send(presence) version_event = q.expect('stream-iq', to=full_jid, query_ns=ns.DISCO_INFO, query_node='http://telepathy.freedesktop.org/homeopathy#0.1') result = make_result_iq(stream, version_event.stanza) query = result.firstChildElement() feature = query.addElement('feature') feature['var'] = ns.CHAT_STATES stream.send(result) sync_stream(q, stream) # Receiving chat states: # Composing... stream.send(make_message(full_jid, state='composing')) changed = q.expect('dbus-signal', signal='ChatStateChanged') handle, state = changed.args assertEquals(foo_handle, handle) assertEquals(cs.CHAT_STATE_COMPOSING, state) # Message! stream.send(make_message(full_jid, body='hello', state='active')) changed = q.expect('dbus-signal', signal='ChatStateChanged') handle, state = changed.args assertEquals(foo_handle, handle) assertEquals(cs.CHAT_STATE_ACTIVE, state) # Sending chat states: # Composing... chan.ChatState.SetChatState(cs.CHAT_STATE_COMPOSING) stream_message = q.expect('stream-message') check_state_notification(stream_message.stanza, 'composing') # XEP 0085: # every content message SHOULD contain an <active/> notification. chan.Text.Send(0, 'hi.') stream_message = q.expect('stream-message') elem = stream_message.stanza assertEquals('chat', elem['type']) check_state_notification(elem, 'active', allow_body=True) def is_body(e): if e.name == 'body': assert e.children[0] == u'hi.', e.toXml() return True return False assert len([x for x in elem.elements() if is_body(x)]) == 1, elem.toXml() # Close the channel without acking the received message. The peer should # get a <gone/> notification, and the channel should respawn. chan.Close() gone, _, _ = q.expect_many( EventPattern('stream-message'), EventPattern('dbus-signal', signal='Closed'), EventPattern('dbus-signal', signal='NewChannel'), ) check_state_notification(gone.stanza, 'gone') # Reusing the proxy object because we happen to know it'll be at the same # path... # Destroy the channel. The peer shouldn't get a <gone/> notification, since # we already said we were gone and haven't sent them any messages to the # contrary. es = [EventPattern('stream-message')] q.forbid_events(es) chan.Destroyable.Destroy() sync_stream(q, stream) # Make the channel anew. path = conn.Requests.CreateChannel( { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, cs.TARGET_HANDLE: foo_handle, })[0] chan = wrap_channel(bus.get_object(conn.bus_name, path), 'Text', ['ChatState', 'Destroyable']) # Close it immediately; the peer should again not get a <gone/> # notification, since we haven't sent any notifications on that channel. chan.Close() sync_stream(q, stream) q.unforbid_events(es) # XEP-0085 §5.1 defines how to negotiate support for chat states with a # contact in the absence of capabilities. This is useful when talking to # invisible contacts, for example. # First, if we receive a message from a contact, containing an <active/> # notification, they support chat states, so we should send them. jid = '*****@*****.**' full_jid = jid + '/GTalk' path = conn.Requests.CreateChannel( { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, cs.TARGET_ID: jid, })[0] chan = wrap_channel(bus.get_object(conn.bus_name, path), 'Text', ['ChatState']) stream.send(make_message(full_jid, body='i am invisible', state='active')) changed = q.expect('dbus-signal', signal='ChatStateChanged') assertEquals(cs.CHAT_STATE_ACTIVE, changed.args[1]) # We've seen them send a chat state notification, so we should send them # notifications when the UI tells us to. chan.ChatState.SetChatState(cs.CHAT_STATE_COMPOSING) stream_message = q.expect('stream-message', to=full_jid) check_state_notification(stream_message.stanza, 'composing') changed = q.expect('dbus-signal', signal='ChatStateChanged') handle, state = changed.args assertEquals(cs.CHAT_STATE_COMPOSING, state) assertEquals(self_handle, handle) chan.Text.Send(0, 'very convincing') stream_message = q.expect('stream-message', to=full_jid) check_state_notification(stream_message.stanza, 'active', allow_body=True) # Now, test the case where we start the negotiation, and the contact # turns out to support chat state notifications. jid = '*****@*****.**' full_jid = jid + '/GTalk' path = conn.Requests.CreateChannel( { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, cs.TARGET_ID: jid, })[0] chan = wrap_channel(bus.get_object(conn.bus_name, path), 'Text', ['ChatState']) # We shouldn't send any notifications until we actually send a message. e = EventPattern('stream-message', to=jid) q.forbid_events([e]) for i in [cs.CHAT_STATE_COMPOSING, cs.CHAT_STATE_INACTIVE, cs.CHAT_STATE_PAUSED, cs.CHAT_STATE_ACTIVE]: chan.ChatState.SetChatState(i) sync_stream(q, stream) q.unforbid_events([e]) # When we send a message, say we're active. chan.Text.Send(0, 'is anyone there?') stream_message = q.expect('stream-message', to=jid) check_state_notification(stream_message.stanza, 'active', allow_body=True) # We get a notification back from our contact. stream.send(make_message(full_jid, state='composing')) # Wait until gabble tells us the chat-state of the remote party has # changed so we know gabble knows chat state notification are supported changed = q.expect('dbus-signal', signal='ChatStateChanged') handle, state = changed.args assertEquals(cs.CHAT_STATE_COMPOSING, state) assertNotEquals(self_handle, handle) # So now we know they support notification, so should send notifications. chan.ChatState.SetChatState(cs.CHAT_STATE_COMPOSING) # This doesn't check whether we're sending to the bare jid, or the # jid+resource. In fact, the notification is sent to the bare jid, because # we only update which jid we send to when we actually receive a message, # not when we receive a notification. wjt thinks this is less surprising # than the alternative: # # • I'm talking to you on my N900, and signed in on my laptop; # • I enter one character in a tab to you on my laptop, and then delete # it; # • Now your messages to me appear on my laptop (until I send you another # one from my N900)! stream_message = q.expect('stream-message') check_state_notification(stream_message.stanza, 'composing') # But! Now they start messaging us from a different client, which *doesn't* # support notifications. other_jid = jid + '/Library' stream.send(make_message(other_jid, body='grr, library computers')) q.expect('dbus-signal', signal='Received') # Okay, we should stop sending typing notifications. e = EventPattern('stream-message', to=other_jid) q.forbid_events([e]) for i in [cs.CHAT_STATE_COMPOSING, cs.CHAT_STATE_INACTIVE, cs.CHAT_STATE_PAUSED, cs.CHAT_STATE_ACTIVE]: chan.ChatState.SetChatState(i) sync_stream(q, stream) q.unforbid_events([e]) # Now, test the case where we start the negotiation, and the contact # does not support chat state notifications jid = '*****@*****.**' full_jid = jid + '/Nonsense' path = conn.Requests.CreateChannel( { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, cs.TARGET_ID: jid, })[0] chan = wrap_channel(bus.get_object(conn.bus_name, path), 'Text', ['ChatState']) # We shouldn't send any notifications until we actually send a message. e = EventPattern('stream-message', to=jid) q.forbid_events([e]) for i in [cs.CHAT_STATE_COMPOSING, cs.CHAT_STATE_INACTIVE, cs.CHAT_STATE_PAUSED, cs.CHAT_STATE_ACTIVE]: chan.ChatState.SetChatState(i) sync_stream(q, stream) q.unforbid_events([e]) # When we send a message, say we're active. chan.Text.Send(0, '#n900 #maemo #zomg #woo #yay http://bit.ly/n900') stream_message = q.expect('stream-message', to=jid) check_state_notification(stream_message.stanza, 'active', allow_body=True) # They reply without a chat state. stream.send(make_message(full_jid, body="posted.")) q.expect('dbus-signal', signal='Received') # Okay, we shouldn't send any more. e = EventPattern('stream-message', to=other_jid) q.forbid_events([e]) for i in [cs.CHAT_STATE_COMPOSING, cs.CHAT_STATE_INACTIVE, cs.CHAT_STATE_PAUSED, cs.CHAT_STATE_ACTIVE]: chan.ChatState.SetChatState(i) sync_stream(q, stream) q.unforbid_events([e]) chan.Text.Send(0, '@stephenfry simmer down') message = q.expect('stream-message') states = [x for x in message.stanza.elements() if x.uri == ns.CHAT_STATES] assertLength(0, states)
def test_hash(q, bus, conn, stream, contact, contact_handle, client): global caps_changed_flag presence = make_presence(contact, status='hello') stream.send(presence) q.expect_many( EventPattern('dbus-signal', signal='PresenceUpdate', args=[{contact_handle: (0L, {u'available': {'message': 'hello'}})}]), EventPattern('dbus-signal', signal='PresencesChanged', args=[{contact_handle: (2, u'available', 'hello')}])) # no special capabilities basic_caps = [(contact_handle, cs.CHANNEL_TYPE_TEXT, 3, 0)] assert conn.Capabilities.GetCapabilities([contact_handle]) == basic_caps # send updated presence with Jingle caps info presence = make_presence(contact, status='hello', caps={'node': client, 'ver': '0.1', }) stream.send(presence) # Gabble looks up our capabilities event = q.expect('stream-iq', to=contact, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + '0.1' # send good reply stream.send(make_caps_disco_reply(stream, event.stanza, jingle_av_features)) # we can now do audio calls event = q.expect('dbus-signal', signal='CapabilitiesChanged') caps_changed_flag = False # send bogus presence caps = { 'node': client, 'ver': 'ceci=nest=pas=un=hash', 'hash': 'sha-1', } presence = make_presence(contact, status='hello', caps=caps) stream.send(presence) # Gabble looks up our capabilities event = q.expect('stream-iq', to=contact, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + caps['ver'] # send bogus reply stream.send(make_caps_disco_reply(stream, event.stanza, ['http://jabber.org/protocol/bogus-feature'])) # don't receive any D-Bus signal sync_dbus(bus, q, conn) sync_stream(q, stream) assert caps_changed_flag == False # send presence with empty caps presence = make_presence(contact, status='hello', caps={'node': client, 'ver': '0.0', }) stream.send(presence) # Gabble looks up our capabilities event = q.expect('stream-iq', to=contact, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + '0.0' # still don't receive any D-Bus signal sync_dbus(bus, q, conn) assert caps_changed_flag == False # send good reply result = make_result_iq(stream, event.stanza) query = result.firstChildElement() stream.send(result) # we can now do nothing event = q.expect('dbus-signal', signal='CapabilitiesChanged') assert caps_changed_flag == True caps_changed_flag = False # send correct presence ver = compute_caps_hash([], jingle_av_features, fake_client_dataforms) caps = { 'node': client, 'ver': ver, 'hash': 'sha-1', } presence = make_presence(contact, status='hello', caps=caps) stream.send(presence) # Gabble looks up our capabilities event = q.expect('stream-iq', to=contact, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + caps['ver'] # don't receive any D-Bus signal sync_dbus(bus, q, conn) assert caps_changed_flag == False # send good reply result = make_caps_disco_reply(stream, event.stanza, jingle_av_features, fake_client_dataforms) stream.send(result) # we can now do audio calls event = q.expect('dbus-signal', signal='CapabilitiesChanged', ) assert caps_changed_flag == True caps_changed_flag = False
def test(q, bus, conn, stream): client = 'http://example.com/perverse-client' contact_bare_jid = '*****@*****.**' contact_with_resource = '[email protected]/hi' contact_handle = conn.get_contact_handle_sync(contact_bare_jid) # Gabble gets a presence stanza from a bare JID, which is a tad surprising. features = [ ns.JINGLE_015, ns.JINGLE_015_AUDIO, ns.JINGLE_015_VIDEO, ns.GOOGLE_P2P, ] caps = { 'node': client, 'hash': 'sha-1', 'ver': compute_caps_hash([], features, {}), } p = make_presence(contact_bare_jid, status='Hello', caps=caps) stream.send(p) # Gabble looks up the hash event = q.expect('stream-iq', to=contact_bare_jid, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assertEquals(client + '#' + caps['ver'], query_node.attributes['node']) # The bare jid replies send_disco_reply(stream, event.stanza, [], features) # Gabble lets us know their caps have changed. (Gabble used to ignore the # reply.) cc, = q.expect_many( EventPattern('dbus-signal', signal='ContactCapabilitiesChanged'), ) assert_rccs_callable(cc.args[0][contact_handle]) # Gabble gets another presence stanza from the bare JID, with different # caps. features.append(ns.TUBES) caps = { 'node': client, 'hash': 'sha-1', 'ver': compute_caps_hash([], features, {}), } p = make_presence(contact_bare_jid, status='Get out the abacus', caps=caps) stream.send(p) # Gabble looks up the new hash disco2 = q.expect('stream-iq', to=contact_bare_jid, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', disco2.stanza)[0] assertEquals(client + '#' + caps['ver'], query_node.attributes['node']) # This time, before the bare JID replies, Gabble gets a presence from the # resourceful jid. features_ = features + [ns.CHAT_STATES] caps = { 'node': client, 'hash': 'sha-1', 'ver': compute_caps_hash([], features_, {}), } p = make_presence(contact_with_resource, status='Count this', caps=caps) stream.send(p) # Gabble throws away presence from the bare JID when it gets presence from # a resource (and vice versa), so it should now say the contact is # incapable. Gabble also looks up the resourceful JID's hash. cc, disco3 = q.expect_many( EventPattern('dbus-signal', signal='ContactCapabilitiesChanged'), EventPattern('stream-iq', to=contact_with_resource, query_ns='http://jabber.org/protocol/disco#info'), ) assert_rccs_not_callable(cc.args[0][contact_handle]) query_node = xpath.queryForNodes('/iq/query', disco3.stanza)[0] assertEquals(client + '#' + caps['ver'], query_node.attributes['node']) # The bare jid replies! Getting a disco reply from a bare JID when we've # got presence from resources used to crash Gabble, but now it just ignores # it. send_disco_reply(stream, disco2.stanza, [], features) # Now the resourceful JID replies: send_disco_reply(stream, disco3.stanza, [], features_) # Gabble should announce that the contact has acquired some caps. cc, = q.expect_many( EventPattern('dbus-signal', signal='ContactCapabilitiesChanged'), ) assert_rccs_callable(cc.args[0][contact_handle])
def test(q, bus, conn, stream): bob = conn.get_contact_handle_sync('*****@*****.**') presence = make_presence('[email protected]/Foo', status='hello') stream.send(presence) q.expect('dbus-signal', signal='PresencesChanged', args=[{bob: (cs.PRESENCE_AVAILABLE, u'available', 'hello')}]) basic_caps = [(bob, cs.CHANNEL_TYPE_TEXT, 3, 0)] # only Text for rcc in get_contacts_capabilities_sync(conn, [bob])[bob]: assertEquals(cs.CHANNEL_TYPE_TEXT, rcc[0].get(cs.CHANNEL_TYPE)) # holding the handle here: see below assertEquals( { bob: { cs.ATTR_CONTACT_CAPABILITIES: get_contacts_capabilities_sync(conn, [bob])[bob], cs.CONN + '/contact-id': '*****@*****.**', }, }, conn.Contacts.GetContactAttributes([bob], [cs.CONN_IFACE_CONTACT_CAPS], True)) # send updated presence with Jingle audio/video caps info. we turn on both # audio and video at the same time to test that all of the capabilities are # discovered before any capabilities change signal is emitted presence = make_presence('[email protected]/Foo', status='hello', caps={ 'node': 'http://telepathy.freedesktop.org/fake-client', 'ver' : '0.1', 'ext' : 'video', }) stream.send(presence) # Gabble looks up both the version and the video bundles, in any order (version_event, video_event) = q.expect_many( EventPattern('stream-iq', to='[email protected]/Foo', query_ns='http://jabber.org/protocol/disco#info', query_node='http://telepathy.freedesktop.org/fake-client#0.1'), EventPattern('stream-iq', to='[email protected]/Foo', query_ns='http://jabber.org/protocol/disco#info', query_node='http://telepathy.freedesktop.org/fake-client#video')) # reply to the video bundle query first - this capability alone is not # sufficient to make us callable result = make_result_iq(stream, video_event.stanza) query = result.firstChildElement() feature = query.addElement('feature') feature['var'] = 'http://jabber.org/protocol/jingle/description/video' stream.send(result) # reply to the version bundle query, which should make us audio and # video callable result = make_result_iq(stream, version_event.stanza) query = result.firstChildElement() feature = query.addElement('feature') feature['var'] = 'http://jabber.org/protocol/jingle' feature = query.addElement('feature') feature['var'] = 'http://jabber.org/protocol/jingle/description/audio' feature = query.addElement('feature') feature['var'] = 'http://www.google.com/transport/p2p' stream.send(result) # we can now do audio and video calls cc, = q.expect_many( EventPattern('dbus-signal', signal='ContactCapabilitiesChanged', predicate=lambda e: check_rccs_callable(e.args[0][bob])), ) assert_rccs_callable(cc.args[0][bob], require_video=True, mutable_contents=True) assertEquals( { bob: { cs.ATTR_CONTACT_CAPABILITIES: cc.args[0][bob], cs.CONN + '/contact-id': '*****@*****.**', }, }, conn.Contacts.GetContactAttributes([bob], [cs.CONN_IFACE_CONTACT_CAPS], True)) # send updated presence without video support presence = make_presence('[email protected]/Foo', status='hello', caps={ 'node': 'http://telepathy.freedesktop.org/fake-client', 'ver' : '0.1', }) stream.send(presence) # we can now do only audio calls (and as a result have the ImmutableStreams # cap) cc, = q.expect_many( EventPattern('dbus-signal', signal='ContactCapabilitiesChanged'), ) assert_rccs_callable(cc.args[0][bob]) assert_rccs_not_callable(cc.args[0][bob], require_audio=False, require_video=True, mutable_contents=False) assertEquals( { bob: { cs.ATTR_CONTACT_CAPABILITIES: cc.args[0][bob], cs.CONN + '/contact-id': '*****@*****.**', }, }, conn.Contacts.GetContactAttributes([bob], [cs.CONN_IFACE_CONTACT_CAPS], True)) # go offline presence = make_presence('[email protected]/Foo', type='unavailable') stream.send(presence) # can't do audio calls any more q.expect_many( EventPattern('dbus-signal', signal='PresencesChanged', args=[{bob: (cs.PRESENCE_OFFLINE, 'offline', '')}]), EventPattern('dbus-signal', signal='ContactCapabilitiesChanged'), ) # Contact went offline. Previously, this test asserted that the handle # became invalid, but that's not guaranteed to happen immediately; so we # now hold the handle (above), to guarantee that it does *not* become # invalid. rccs = get_contacts_capabilities_sync(conn, [bob])[bob] for rcc in rccs: assertEquals(cs.CHANNEL_TYPE_TEXT, rcc[0].get(cs.CHANNEL_TYPE)) assertEquals( { bob: { cs.ATTR_CONTACT_CAPABILITIES: rccs, cs.CONN + '/contact-id': '*****@*****.**', }, }, conn.Contacts.GetContactAttributes([bob], [cs.CONN_IFACE_CONTACT_CAPS], True)) # What about a handle that's not valid? assertEquals({}, conn.Contacts.GetContactAttributes( [31337], [cs.CONN_IFACE_CONTACT_CAPS], False))
def test(q, bus, conn, stream): self_handle = conn.Properties.Get(cs.CONN, "SelfHandle") jid = '*****@*****.**' full_jid = '[email protected]/Foo' foo_handle = conn.get_contact_handle_sync(jid) path = conn.Requests.CreateChannel({ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, cs.TARGET_HANDLE: foo_handle, })[0] chan = wrap_channel(bus.get_object(conn.bus_name, path), 'Text') presence = make_presence(full_jid, status='hello', caps={ 'node': 'http://telepathy.freedesktop.org/homeopathy', 'ver': '0.1', }) stream.send(presence) version_event = q.expect( 'stream-iq', to=full_jid, query_ns=ns.DISCO_INFO, query_node='http://telepathy.freedesktop.org/homeopathy#0.1') result = make_result_iq(stream, version_event.stanza) query = result.firstChildElement() feature = query.addElement('feature') feature['var'] = ns.CHAT_STATES stream.send(result) sync_stream(q, stream) states = chan.Properties.Get(cs.CHANNEL_IFACE_CHAT_STATE, 'ChatStates') assertEquals(cs.CHAT_STATE_INACTIVE, states.get(self_handle, cs.CHAT_STATE_INACTIVE)) assertEquals(cs.CHAT_STATE_INACTIVE, states.get(foo_handle, cs.CHAT_STATE_INACTIVE)) # Receiving chat states: # Composing... stream.send(make_message(full_jid, state='composing')) changed = q.expect('dbus-signal', signal='ChatStateChanged', path=chan.object_path) handle, state = changed.args assertEquals(foo_handle, handle) assertEquals(cs.CHAT_STATE_COMPOSING, state) states = chan.Properties.Get(cs.CHANNEL_IFACE_CHAT_STATE, 'ChatStates') assertEquals(cs.CHAT_STATE_INACTIVE, states.get(self_handle, cs.CHAT_STATE_INACTIVE)) assertEquals(cs.CHAT_STATE_COMPOSING, states.get(foo_handle, cs.CHAT_STATE_INACTIVE)) # Message! stream.send(make_message(full_jid, body='hello', state='active')) changed = q.expect('dbus-signal', signal='ChatStateChanged', path=chan.object_path) handle, state = changed.args assertEquals(foo_handle, handle) assertEquals(cs.CHAT_STATE_ACTIVE, state) states = chan.Properties.Get(cs.CHANNEL_IFACE_CHAT_STATE, 'ChatStates') assertEquals(cs.CHAT_STATE_INACTIVE, states.get(self_handle, cs.CHAT_STATE_INACTIVE)) assertEquals(cs.CHAT_STATE_ACTIVE, states.get(foo_handle, cs.CHAT_STATE_INACTIVE)) # Assert that a redundant chat-state change doesn't emit a signal forbidden = [ EventPattern('dbus-signal', signal='ChatStateChanged', args=[foo_handle, cs.CHAT_STATE_ACTIVE]) ] q.forbid_events(forbidden) m = domish.Element((None, 'message')) m['from'] = '[email protected]/Foo' m['type'] = 'chat' m.addElement((ns.CHAT_STATES, 'active')) m.addElement('body', content='hello') stream.send(m) sync_dbus(bus, q, conn) sync_stream(q, stream) q.unforbid_events(forbidden) # Sending chat states: # Composing... chan.ChatState.SetChatState(cs.CHAT_STATE_COMPOSING) stream_message = q.expect('stream-message') check_state_notification(stream_message.stanza, 'composing') states = chan.Properties.Get(cs.CHANNEL_IFACE_CHAT_STATE, 'ChatStates') assertEquals(cs.CHAT_STATE_COMPOSING, states.get(self_handle, cs.CHAT_STATE_INACTIVE)) assertEquals(cs.CHAT_STATE_ACTIVE, states.get(foo_handle, cs.CHAT_STATE_INACTIVE)) # XEP 0085: # every content message SHOULD contain an <active/> notification. chan.send_msg_sync('hi.') stream_message = q.expect('stream-message') elem = stream_message.stanza assertEquals('chat', elem['type']) check_state_notification(elem, 'active', allow_body=True) states = chan.Properties.Get(cs.CHANNEL_IFACE_CHAT_STATE, 'ChatStates') assertEquals(cs.CHAT_STATE_ACTIVE, states.get(self_handle, cs.CHAT_STATE_INACTIVE)) assertEquals(cs.CHAT_STATE_ACTIVE, states.get(foo_handle, cs.CHAT_STATE_INACTIVE)) def is_body(e): if e.name == 'body': assert e.children[0] == u'hi.', e.toXml() return True return False assert len([x for x in elem.elements() if is_body(x)]) == 1, elem.toXml() # Close the channel without acking the received message. The peer should # get a <gone/> notification, and the channel should respawn. chan.Close() gone, _ = q.expect_many( EventPattern('stream-message'), EventPattern('dbus-signal', signal='Closed'), ) check_state_notification(gone.stanza, 'gone') # Reusing the proxy object because we happen to know it'll be at the same # path... # Destroy the channel. The peer shouldn't get a <gone/> notification, since # we already said we were gone and haven't sent them any messages to the # contrary. es = [EventPattern('stream-message')] q.forbid_events(es) chan.Destroyable.Destroy() sync_stream(q, stream) # Make the channel anew. path = conn.Requests.CreateChannel({ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, cs.TARGET_HANDLE: foo_handle, })[0] chan = wrap_channel(bus.get_object(conn.bus_name, path), 'Text') # Close it immediately; the peer should again not get a <gone/> # notification, since we haven't sent any notifications on that channel. chan.Close() sync_stream(q, stream) q.unforbid_events(es) # XEP-0085 §5.1 defines how to negotiate support for chat states with a # contact in the absence of capabilities. This is useful when talking to # invisible contacts, for example. # First, if we receive a message from a contact, containing an <active/> # notification, they support chat states, so we should send them. jid = '*****@*****.**' full_jid = jid + '/GTalk' path = conn.Requests.CreateChannel({ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, cs.TARGET_ID: jid, })[0] chan = wrap_channel(bus.get_object(conn.bus_name, path), 'Text') stream.send(make_message(full_jid, body='i am invisible', state='active')) changed = q.expect('dbus-signal', signal='ChatStateChanged', path=chan.object_path) assertEquals(cs.CHAT_STATE_ACTIVE, changed.args[1]) # We've seen them send a chat state notification, so we should send them # notifications when the UI tells us to. chan.ChatState.SetChatState(cs.CHAT_STATE_COMPOSING) stream_message = q.expect('stream-message', to=full_jid) check_state_notification(stream_message.stanza, 'composing') changed = q.expect('dbus-signal', signal='ChatStateChanged', path=chan.object_path) handle, state = changed.args assertEquals(cs.CHAT_STATE_COMPOSING, state) assertEquals(self_handle, handle) chan.send_msg_sync('very convincing') stream_message = q.expect('stream-message', to=full_jid) check_state_notification(stream_message.stanza, 'active', allow_body=True) # Now, test the case where we start the negotiation, and the contact # turns out to support chat state notifications. jid = '*****@*****.**' full_jid = jid + '/GTalk' path = conn.Requests.CreateChannel({ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, cs.TARGET_ID: jid, })[0] chan = wrap_channel(bus.get_object(conn.bus_name, path), 'Text') # We shouldn't send any notifications until we actually send a message. # But ChatStateChanged is still emitted locally e = EventPattern('stream-message', to=jid) q.forbid_events([e]) for i in [ cs.CHAT_STATE_ACTIVE, cs.CHAT_STATE_COMPOSING, cs.CHAT_STATE_PAUSED, cs.CHAT_STATE_INACTIVE ]: chan.ChatState.SetChatState(i) changed = q.expect('dbus-signal', signal='ChatStateChanged', path=chan.object_path) handle, state = changed.args assertEquals(i, state) assertEquals(self_handle, handle) sync_stream(q, stream) q.unforbid_events([e]) # When we send a message, say we're active. chan.send_msg_sync('is anyone there?') stream_message = q.expect('stream-message', to=jid) check_state_notification(stream_message.stanza, 'active', allow_body=True) # The D-Bus property changes, too changed = q.expect('dbus-signal', signal='ChatStateChanged', path=chan.object_path) handle, state = changed.args assertEquals(cs.CHAT_STATE_ACTIVE, state) assertEquals(self_handle, handle) # We get a notification back from our contact. stream.send(make_message(full_jid, state='composing')) # Wait until gabble tells us the chat-state of the remote party has # changed so we know gabble knows chat state notification are supported changed = q.expect('dbus-signal', signal='ChatStateChanged', path=chan.object_path) handle, state = changed.args assertEquals(cs.CHAT_STATE_COMPOSING, state) assertNotEquals(foo_handle, handle) # So now we know they support notification, so should send notifications. chan.ChatState.SetChatState(cs.CHAT_STATE_COMPOSING) # This doesn't check whether we're sending to the bare jid, or the # jid+resource. In fact, the notification is sent to the bare jid, because # we only update which jid we send to when we actually receive a message, # not when we receive a notification. wjt thinks this is less surprising # than the alternative: # # • I'm talking to you on my N900, and signed in on my laptop; # • I enter one character in a tab to you on my laptop, and then delete # it; # • Now your messages to me appear on my laptop (until I send you another # one from my N900)! stream_message = q.expect('stream-message') check_state_notification(stream_message.stanza, 'composing') # The D-Bus property changes, too changed = q.expect('dbus-signal', signal='ChatStateChanged', path=chan.object_path) handle, state = changed.args assertEquals(cs.CHAT_STATE_COMPOSING, state) assertEquals(self_handle, handle) # But! Now they start messaging us from a different client, which *doesn't* # support notifications. other_jid = jid + '/Library' stream.send(make_message(other_jid, body='grr, library computers')) q.expect('dbus-signal', signal='MessageReceived') # Okay, we should stop sending typing notifications. e = EventPattern('stream-message', to=other_jid) q.forbid_events([e]) for i in [ cs.CHAT_STATE_COMPOSING, cs.CHAT_STATE_INACTIVE, cs.CHAT_STATE_PAUSED, cs.CHAT_STATE_ACTIVE ]: chan.ChatState.SetChatState(i) sync_stream(q, stream) q.unforbid_events([e]) # Now, test the case where we start the negotiation, and the contact # does not support chat state notifications jid = '*****@*****.**' full_jid = jid + '/Nonsense' path = conn.Requests.CreateChannel({ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, cs.TARGET_ID: jid, })[0] chan = wrap_channel(bus.get_object(conn.bus_name, path), 'Text') # We shouldn't send any notifications until we actually send a message. e = EventPattern('stream-message', to=jid) q.forbid_events([e]) for i in [ cs.CHAT_STATE_COMPOSING, cs.CHAT_STATE_INACTIVE, cs.CHAT_STATE_PAUSED, cs.CHAT_STATE_ACTIVE ]: chan.ChatState.SetChatState(i) sync_stream(q, stream) q.unforbid_events([e]) # When we send a message, say we're active. chan.send_msg_sync('#n900 #maemo #zomg #woo #yay http://bit.ly/n900') stream_message = q.expect('stream-message', to=jid) check_state_notification(stream_message.stanza, 'active', allow_body=True) # They reply without a chat state. stream.send(make_message(full_jid, body="posted.")) q.expect('dbus-signal', signal='MessageReceived') # Okay, we shouldn't send any more. e = EventPattern('stream-message', to=other_jid) q.forbid_events([e]) for i in [ cs.CHAT_STATE_COMPOSING, cs.CHAT_STATE_INACTIVE, cs.CHAT_STATE_PAUSED, cs.CHAT_STATE_ACTIVE ]: chan.ChatState.SetChatState(i) sync_stream(q, stream) q.unforbid_events([e]) chan.send_msg_sync('@stephenfry simmer down') message = q.expect('stream-message') states = [x for x in message.stanza.elements() if x.uri == ns.CHAT_STATES] assertLength(0, states)
def test(q, bus, conn, stream): iq_event = q.expect('stream-iq', to=None, query_ns='vcard-temp', query_name='vCard') # When we start, there is no avatar acknowledge_iq(stream, iq_event.stanza) self_handle = conn.GetSelfHandle() # Another resource confirms we have no avatar. We don't request our vCard # because we already know there is no avatar presence_stanza = make_presence('test@localhost/noavatar', to='test@localhost/Resource', show='away', status='At the pub', photo="") q.forbid_events([avatar_request_event, avatar_retrieved_event]) # Gabble must resist temptation to send vCard requests even with several # presence stanza sent! stream.send(presence_stanza) stream.send(presence_stanza) sync_stream(q, stream) # Twice because the vCard request is done in sync_stream(q, stream) # g_idle_add q.unforbid_events([avatar_request_event, avatar_retrieved_event]) # Request on the first contact. Test the cache. handle = conn.RequestHandles(cs.HT_CONTACT, ['*****@*****.**'])[0] test_get_avatar(q, bus, conn, stream, '*****@*****.**', handle, in_cache=False) test_get_avatar(q, bus, conn, stream, '*****@*****.**', handle, in_cache=True) # Request another vCard and get resource-constraint busy_contact = '*****@*****.**' busy_handle = conn.RequestHandles(cs.HT_CONTACT, [busy_contact])[0] conn.Avatars.RequestAvatars([busy_handle]) iq_event = q.expect('stream-iq', to=busy_contact, query_ns='vcard-temp', query_name='vCard') iq = iq_event.stanza error = domish.Element((None, 'error')) error['code'] = '500' error['type'] = 'wait' error.addElement((ns.STANZA, 'resource-constraint')) q.forbid_events([avatar_retrieved_event, avatar_request_event]) send_error_reply(stream, iq, error) # Request the same vCard again during the suspended delay # We should not get the avatar conn.Avatars.RequestAvatars([busy_handle]) sync_stream(q, stream) sync_dbus(bus, q, conn) q.unforbid_events([avatar_retrieved_event, avatar_request_event]) # Request on a different contact, on another server # We should get the avatar handle = conn.RequestHandles(cs.HT_CONTACT, ['*****@*****.**'])[0] test_get_avatar(q, bus, conn, stream, '*****@*****.**', handle) # Try again the contact on the busy server. # We should not get the avatar # Note: the timeout is 3 seconds for the test suites. We assume that # a few stanza with be processed fast enough to avoid the race. q.forbid_events([avatar_retrieved_event, avatar_request_event]) conn.Avatars.RequestAvatars([busy_handle]) sync_stream(q, stream) sync_dbus(bus, q, conn) q.unforbid_events([avatar_retrieved_event, avatar_request_event]) # After 3 seconds, we receive a new vCard request on the busy server iq_event = q.expect('stream-iq', to=busy_contact, query_ns='vcard-temp', query_name='vCard') iq = make_result_iq(stream, iq_event.stanza) vcard = iq.firstChildElement() photo = vcard.addElement('PHOTO') photo.addElement('TYPE', content='image/png') photo.addElement('BINVAL', content=base64.b64encode('hello')) stream.send(iq) event = q.expect('dbus-signal', signal='AvatarRetrieved') assertEquals(busy_handle, event.args[0]) assertEquals(hashlib.sha1('hello').hexdigest(), event.args[1]) assertEquals('hello', event.args[2]) assertEquals('image/png', event.args[3]) # Test with our own avatar test@localhost/Resource2 presence_stanza = make_presence('test@localhost/Resource2', to='test@localhost/Resource', show='away', status='At the pub', photo=hashlib.sha1(':-D').hexdigest()) stream.send(presence_stanza) iq_event = q.expect('stream-iq', to=None, query_ns='vcard-temp', query_name='vCard') iq = make_result_iq(stream, iq_event.stanza) vcard = iq.firstChildElement() photo = vcard.addElement('PHOTO') photo.addElement('TYPE', content='image/png') photo.addElement('BINVAL', content=base64.b64encode(':-D')) # do not send the vCard reply now. First, send another presence. q.forbid_events([avatar_request_event]) stream.send(presence_stanza) sync_stream(q, stream) # Now send the reply. stream.send(iq) # Which results in an AvatarUpdated signal event = q.expect('dbus-signal', signal='AvatarUpdated') assertEquals(self_handle, event.args[0]) assertEquals(hashlib.sha1(':-D').hexdigest(), event.args[1]) # So Gabble has the right hash, and no need to ask the vCard again stream.send(presence_stanza) sync_stream(q, stream) q.unforbid_events([avatar_request_event]) # But if the hash is different, the vCard is asked again presence_stanza = make_presence('test@localhost/Resource2', to='test@localhost/Resource', show='away', status='At the pub', photo=hashlib.sha1('\o/').hexdigest()) stream.send(presence_stanza) iq_event = q.expect('stream-iq', to=None, query_ns='vcard-temp', query_name='vCard') iq = make_result_iq(stream, iq_event.stanza) vcard = iq.firstChildElement() photo = vcard.addElement('PHOTO') photo.addElement('TYPE', content='image/png') photo.addElement('BINVAL', content=base64.b64encode('\o/')) stream.send(iq) event = q.expect('dbus-signal', signal='AvatarUpdated') assertEquals(self_handle, event.args[0]) assertEquals(hashlib.sha1('\o/').hexdigest(), event.args[1]) # Gabble must reply without asking the vCard to the server because the # avatar must be in the cache q.forbid_events([avatar_request_event]) data, mime = conn.Avatars.RequestAvatar(self_handle, byte_arrays=True) assertEquals('\o/', data) data, mime = conn.Avatars.RequestAvatar(handle, byte_arrays=True) assertEquals('hello', data) q.unforbid_events([avatar_request_event]) # First, ensure the pipeline is full contacts = ['*****@*****.**' % i for i in range(1, 100) ] handles = conn.RequestHandles(cs.HT_CONTACT, contacts) conn.Avatars.RequestAvatars(handles) # Then, request yet another avatar. The request will time out before # the IQ is sent, which used to trigger a crash in Gabble # (LP#445847). So, we assert that the error is NotAvailable (rather # than the error returned when the service crashes). try: conn.Avatars.RequestAvatar(handles[-1]) except dbus.DBusException, e: assertEquals(cs.NOT_AVAILABLE, e.get_dbus_name())
def test_two_clients(q, bus, conn, stream, contact1, contact2, contact_handle1, contact_handle2, client, broken_hash): presence = make_presence(contact1, status='hello') stream.send(presence) q.expect('dbus-signal', signal='PresencesChanged', args=[{contact_handle1: (2, u'available', 'hello')}]) presence = make_presence(contact2, status='hello') stream.send(presence) q.expect('dbus-signal', signal='PresencesChanged', args=[{contact_handle2: (2, u'available', 'hello')}]) # no special capabilities for h in (contact_handle1, contact_handle2): for rcc in get_contacts_capabilities_sync(conn, [h])[h]: assertEquals(cs.CHANNEL_TYPE_TEXT, rcc[0].get(cs.CHANNEL_TYPE)) # send updated presence with Jingle caps info ver = compute_caps_hash(some_identities, jingle_av_features, {}) caps = { 'node': client, 'ver': ver, 'hash': 'sha-1', } presence = make_presence(contact1, status='hello', caps=caps) stream.send(presence) presence = make_presence(contact2, status='hello', caps=caps) stream.send(presence) # Gabble looks up our capabilities event = q.expect('stream-iq', to=contact1, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + ver # don't receive any D-Bus signal forbidden = [ EventPattern('dbus-signal', signal='ContactCapabilitiesChanged'), ] q.forbid_events(forbidden) sync_dbus(bus, q, conn) q.unforbid_events(forbidden) result = make_caps_disco_reply( stream, event.stanza, some_identities, jingle_av_features) if broken_hash: # make the hash break! query = result.firstChildElement() query.addElement('feature')['var'] = 'http://example.com/another-feature' stream.send(result) if broken_hash: # Gabble looks up our capabilities again because the first contact # failed to provide a valid hash event = q.expect('stream-iq', to=contact2, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + ver # don't receive any D-Bus signal q.forbid_events(forbidden) sync_dbus(bus, q, conn) q.unforbid_events(forbidden) # send good reply send_disco_reply(stream, event.stanza, some_identities, jingle_av_features) # we can now do audio calls cc, = q.expect_many( EventPattern('dbus-signal', signal='ContactCapabilitiesChanged', predicate=lambda e: contact_handle2 in e.args[0]), ) assert_rccs_callable(cc.args[0][contact_handle2]) if not broken_hash: # if the first contact failed to provide a good hash, it does not # deserve its capabilities to be understood by Gabble! cc, = q.expect_many( EventPattern('dbus-signal', signal='ContactCapabilitiesChanged', predicate=lambda e: contact_handle1 in e.args[0]), ) assert_rccs_callable(cc.args[0][contact_handle1]) # don't receive any further signals q.forbid_events(forbidden) sync_dbus(bus, q, conn) q.unforbid_events(forbidden)
def test_local_queueing(q, bus, conn, stream): assertContains( cs.CONN_IFACE_POWER_SAVING, conn.Get(cs.CONN, "Interfaces", dbus_interface=cs.PROPERTIES_IFACE)) assertEquals( False, conn.Get(cs.CONN_IFACE_POWER_SAVING, "PowerSavingActive", dbus_interface=cs.PROPERTIES_IFACE)) event = q.expect('stream-iq', to=None, query_ns='vcard-temp', query_name='vCard') acknowledge_iq(stream, event.stanza) presence_update = [EventPattern('dbus-signal', signal='PresencesChanged')] q.forbid_events(presence_update) call_async(q, conn.PowerSaving, 'SetPowerSaving', True) q.expect_many( EventPattern('dbus-return', method='SetPowerSaving'), EventPattern('dbus-signal', signal='PowerSavingChanged', args=[True])) assertEquals( True, conn.Get(cs.CONN_IFACE_POWER_SAVING, "PowerSavingActive", dbus_interface=cs.PROPERTIES_IFACE)) # These presence stanzas should be queued stream.send(make_presence('*****@*****.**', show='away', status='At the pub')) stream.send( make_presence('*****@*****.**', show='xa', status='Somewhere over the rainbow')) # Pep notifications too message = elem('message', from_='*****@*****.**')(elem( (ns.PUBSUB_EVENT), 'event')(elem('items', node=ns.NICK)(elem('item')(elem(ns.NICK, 'nick')(u'Robert'))))) stream.send(message.toXml()) sync_dbus(bus, q, conn) q.unforbid_events(presence_update) # Incoming important stanza will flush the queue m = domish.Element((None, 'message')) m['from'] = '[email protected]/Pidgin' m['id'] = '123' m['type'] = 'chat' m.addElement('body', content='important message') stream.send(m) # Presence updates should come in the original order ... p1 = q.expect('dbus-signal', signal='PresencesChanged') p2 = q.expect('dbus-signal', signal='PresencesChanged') assertEquals('away', p1.args[0].values()[0][1]) assertEquals('xa', p2.args[0].values()[0][1]) # .. followed by the result of PEP notification .. event = q.expect('dbus-signal', signal='AliasesChanged') # .. and finally the message that flushed the stanza queue q.expect('dbus-signal', signal='NewChannels') sync_stream(q, stream) q.forbid_events(presence_update) stream.send(make_presence('*****@*****.**', show='away', status='Home')) # Carl's presence update is queued sync_dbus(bus, q, conn) q.unforbid_events(presence_update) # Disable powersaving, flushing the queue conn.PowerSaving.SetPowerSaving(False) q.expect('dbus-signal', signal='PresencesChanged')
def test(q, bus, conn, stream): iq_event = q.expect('stream-iq', to=None, query_ns='vcard-temp', query_name='vCard') # When we start, there is no avatar acknowledge_iq(stream, iq_event.stanza) self_handle = conn.Properties.Get(cs.CONN, "SelfHandle") # Another resource confirms we have no avatar. We don't request our vCard # because we already know there is no avatar presence_stanza = make_presence('test@localhost/noavatar', to='test@localhost/Resource', show='away', status='At the pub', photo="") q.forbid_events([avatar_request_event, avatar_retrieved_event]) # Gabble must resist temptation to send vCard requests even with several # presence stanza sent! stream.send(presence_stanza) stream.send(presence_stanza) sync_stream(q, stream) # Twice because the vCard request is done in sync_stream(q, stream) # g_idle_add q.unforbid_events([avatar_request_event, avatar_retrieved_event]) # Request on the first contact. Test the cache. handle = conn.get_contact_handle_sync('*****@*****.**') test_get_avatar(q, bus, conn, stream, '*****@*****.**', handle, in_cache=False) test_get_avatar(q, bus, conn, stream, '*****@*****.**', handle, in_cache=True) # Request another vCard and get resource-constraint busy_contact = '*****@*****.**' busy_handle = conn.get_contact_handle_sync(busy_contact) conn.Avatars.RequestAvatars([busy_handle]) iq_event = q.expect('stream-iq', to=busy_contact, query_ns='vcard-temp', query_name='vCard') iq = iq_event.stanza error = domish.Element((None, 'error')) error['code'] = '500' error['type'] = 'wait' error.addElement((ns.STANZA, 'resource-constraint')) q.forbid_events([avatar_retrieved_event, avatar_request_event]) send_error_reply(stream, iq, error) # Request the same vCard again during the suspended delay # We should not get the avatar conn.Avatars.RequestAvatars([busy_handle]) sync_stream(q, stream) sync_dbus(bus, q, conn) q.unforbid_events([avatar_retrieved_event, avatar_request_event]) # Request on a different contact, on another server # We should get the avatar handle = conn.get_contact_handle_sync('*****@*****.**') test_get_avatar(q, bus, conn, stream, '*****@*****.**', handle) # Try again the contact on the busy server. # We should not get the avatar # Note: the timeout is 3 seconds for the test suites. We assume that # a few stanza with be processed fast enough to avoid the race. q.forbid_events([avatar_retrieved_event, avatar_request_event]) conn.Avatars.RequestAvatars([busy_handle]) sync_stream(q, stream) sync_dbus(bus, q, conn) q.unforbid_events([avatar_retrieved_event, avatar_request_event]) # After 3 seconds, we receive a new vCard request on the busy server iq_event = q.expect('stream-iq', to=busy_contact, query_ns='vcard-temp', query_name='vCard') iq = make_result_iq(stream, iq_event.stanza) vcard = iq.firstChildElement() photo = vcard.addElement('PHOTO') photo.addElement('TYPE', content='image/png') photo.addElement('BINVAL', content=base64.b64encode(b'hello').decode()) stream.send(iq) event = q.expect('dbus-signal', signal='AvatarRetrieved') assertEquals(busy_handle, event.args[0]) assertEquals(hashlib.sha1(b'hello').hexdigest(), event.args[1]) assertEquals(b'hello', event.args[2]) assertEquals('image/png', event.args[3]) # Test with our own avatar test@localhost/Resource2 presence_stanza = make_presence('test@localhost/Resource2', to='test@localhost/Resource', show='away', status='At the pub', photo=hashlib.sha1(b':-D').hexdigest()) stream.send(presence_stanza) iq_event = q.expect('stream-iq', to=None, query_ns='vcard-temp', query_name='vCard') iq = make_result_iq(stream, iq_event.stanza) vcard = iq.firstChildElement() photo = vcard.addElement('PHOTO') photo.addElement('TYPE', content='image/png') photo.addElement('BINVAL', content=base64.b64encode(b':-D').decode()) # do not send the vCard reply now. First, send another presence. q.forbid_events([avatar_request_event]) stream.send(presence_stanza) sync_stream(q, stream) # Now send the reply. stream.send(iq) # Which results in an AvatarUpdated signal event = q.expect('dbus-signal', signal='AvatarUpdated') assertEquals(self_handle, event.args[0]) assertEquals(hashlib.sha1(b':-D').hexdigest(), event.args[1]) # So Gabble has the right hash, and no need to ask the vCard again stream.send(presence_stanza) sync_stream(q, stream) q.unforbid_events([avatar_request_event]) # But if the hash is different, the vCard is asked again presence_stanza = make_presence('test@localhost/Resource2', to='test@localhost/Resource', show='away', status='At the pub', photo=hashlib.sha1(b'\o/').hexdigest()) stream.send(presence_stanza) iq_event = q.expect('stream-iq', to=None, query_ns='vcard-temp', query_name='vCard') iq = make_result_iq(stream, iq_event.stanza) vcard = iq.firstChildElement() photo = vcard.addElement('PHOTO') photo.addElement('TYPE', content='image/png') photo.addElement('BINVAL', content=base64.b64encode(b'\o/').decode()) stream.send(iq) event = q.expect('dbus-signal', signal='AvatarUpdated') assertEquals(self_handle, event.args[0]) assertEquals(hashlib.sha1(b'\o/').hexdigest(), event.args[1]) # Gabble must reply without asking the vCard to the server because the # avatar must be in the cache q.forbid_events([avatar_request_event]) conn.Avatars.RequestAvatars([self_handle]) e = q.expect('dbus-signal', signal='AvatarRetrieved') assertEquals(b'\o/', e.args[2]) conn.Avatars.RequestAvatars([handle]) e = q.expect('dbus-signal', signal='AvatarRetrieved') assertEquals(b'hello', e.args[2]) q.unforbid_events([avatar_request_event]) # First, ensure the pipeline is full contacts = ['*****@*****.**' % i for i in range(1, 100)] handles = conn.get_contact_handles_sync(contacts) conn.Avatars.RequestAvatars(handles) # Then, request yet another avatar. The request will time out before # the IQ is sent, which used to trigger a crash in Gabble # (LP#445847). conn.Avatars.RequestAvatars([handles[-1]]) sync_dbus(bus, q, conn)
def test_ft_caps_from_contact(q, bus, conn, stream, contact, contact_handle, client): global run run += 1 conn_caps_iface = dbus.Interface(conn, cs.CONN_IFACE_CONTACT_CAPS) conn_contacts_iface = dbus.Interface(conn, cs.CONN_IFACE_CONTACTS) # send presence with no FT cap presence = make_presence(contact, status='hello') c = presence.addElement((ns.CAPS, 'c')) c['node'] = client c['ver'] = compute_caps_hash(['client/pc//jingleshareutils-%d' % run], [], {}) c['ext'] = "" stream.send(presence) # Gabble looks up our capabilities event = q.expect('stream-iq', to=contact, query_ns=ns.DISCO_INFO) query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + c['ver'] # send good reply result = make_result_iq(stream, event.stanza) query = result.firstChildElement() query['node'] = client + '#' + c['ver'] stream.send(result) # no change in ContactCapabilities, so no signal ContactCapabilitiesChanged sync_stream(q, stream) # no special capabilities basic_caps = dbus.Dictionary({contact_handle: [(text_fixed_properties, text_allowed_properties)]}) caps = conn_caps_iface.GetContactCapabilities([contact_handle]) assert caps == basic_caps, caps # test again, to check GetContactCapabilities does not have side effect caps = conn_caps_iface.GetContactCapabilities([contact_handle]) assert caps == basic_caps, caps # check the Contacts interface give the same caps caps_via_contacts_iface = conn_contacts_iface.GetContactAttributes( [contact_handle], [cs.CONN_IFACE_CONTACT_CAPS], False) \ [contact_handle][cs.ATTR_CONTACT_CAPABILITIES] assert caps_via_contacts_iface == caps[contact_handle], \ caps_via_contacts_iface # send presence with ft capa presence = make_presence(contact, status='hello') c = presence.addElement((ns.CAPS, 'c')) c['node'] = client c['ext'] = "share-v1" c['ver'] = compute_caps_hash([], [], {}) stream.send(presence) # Gabble looks up our capabilities event = q.expect('stream-iq', to=contact, query_ns=ns.DISCO_INFO) query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + c['ext'] # send good reply result = make_result_iq(stream, event.stanza) query = result.firstChildElement() query['node'] = client + '#' + c['ext'] feature = query.addElement('feature') feature['var'] = ns.GOOGLE_FEAT_SHARE stream.send(result) generic_ft_caps = dbus.Dictionary({contact_handle: [(text_fixed_properties, text_allowed_properties), (ft_fixed_properties, ft_allowed_properties)]}) event = q.expect('dbus-signal', signal='ContactCapabilitiesChanged') assert len(event.args) == 1 assert event.args[0] == generic_ft_caps caps = conn_caps_iface.GetContactCapabilities([contact_handle]) assert caps == generic_ft_caps, caps # test again, to check GetContactCapabilities does not have side effect caps = conn_caps_iface.GetContactCapabilities([contact_handle]) assert caps == generic_ft_caps, caps # check the Contacts interface give the same caps caps_via_contacts_iface = conn_contacts_iface.GetContactAttributes( [contact_handle], [cs.CONN_IFACE_CONTACT_CAPS], False) \ [contact_handle][cs.ATTR_CONTACT_CAPABILITIES] assert caps_via_contacts_iface == caps[contact_handle], \ caps_via_contacts_iface
def test_hash(q, bus, conn, stream, contact, contact_handle, client): presence = make_presence(contact, status='hello') stream.send(presence) q.expect('dbus-signal', signal='PresencesChanged', args=[{contact_handle: (2, u'available', 'hello')}]) # no special capabilities for rcc in get_contacts_capabilities_sync(conn, [contact_handle])[contact_handle]: assertEquals(cs.CHANNEL_TYPE_TEXT, rcc[0].get(cs.CHANNEL_TYPE)) # send updated presence with Jingle caps info presence = make_presence(contact, status='hello', caps={'node': client, 'ver': '0.1', }) stream.send(presence) # Gabble looks up our capabilities event = q.expect('stream-iq', to=contact, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + '0.1' # send good reply send_disco_reply(stream, event.stanza, [], jingle_av_features) # we can now do audio calls cc, = q.expect_many( EventPattern('dbus-signal', signal='ContactCapabilitiesChanged'), ) assert_rccs_callable(cc.args[0][contact_handle]) assertEquals(cc.args[0], get_contacts_capabilities_sync(conn, [contact_handle])) # Send presence without any capabilities. XEP-0115 §8.4 Caps Optimization # says “receivers of presence notifications MUST NOT expect an annotation # on every presence notification they receive”, so the contact should still # be media-capable afterwards. stream.send(make_presence(contact, status='very capable')) q.expect('dbus-signal', signal='PresencesChanged', args=[{contact_handle: (2, u'available', 'very capable')}]) # still exactly the same capabilities assertEquals(cc.args[0], get_contacts_capabilities_sync(conn, [contact_handle])) # send bogus presence caps = { 'node': client, 'ver': 'ceci=nest=pas=un=hash', 'hash': 'sha-1', } presence = make_presence(contact, status='hello', caps=caps) stream.send(presence) # Gabble looks up our capabilities event = q.expect('stream-iq', to=contact, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + caps['ver'] # send bogus reply send_disco_reply(stream, event.stanza, [], ['http://jabber.org/protocol/bogus-feature']) # don't receive any D-Bus signal forbidden = [ EventPattern('dbus-signal', signal='ContactCapabilitiesChanged'), ] q.forbid_events(forbidden) sync_dbus(bus, q, conn) sync_stream(q, stream) # send presence with empty caps presence = make_presence(contact, status='hello', caps={'node': client, 'ver': '0.0', }) stream.send(presence) # Gabble looks up our capabilities event = q.expect('stream-iq', to=contact, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + '0.0' # still don't receive any D-Bus signal sync_dbus(bus, q, conn) # send good reply q.unforbid_events(forbidden) result = make_result_iq(stream, event.stanza) query = result.firstChildElement() stream.send(result) # we can now do nothing cc, = q.expect_many( EventPattern('dbus-signal', signal='ContactCapabilitiesChanged'), ) for rcc in cc.args[0][contact_handle]: assertEquals(cs.CHANNEL_TYPE_TEXT, rcc[0].get(cs.CHANNEL_TYPE)) assert_rccs_not_callable(cc.args[0][contact_handle]) assertEquals(cc.args[0], get_contacts_capabilities_sync(conn, [contact_handle])) # send correct presence ver = compute_caps_hash(some_identities, jingle_av_features, fake_client_dataforms) caps = { 'node': client, 'ver': ver, 'hash': 'sha-1', } presence = make_presence(contact, status='hello', caps=caps) stream.send(presence) # Gabble looks up our capabilities event = q.expect('stream-iq', to=contact, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + caps['ver'] # don't receive any D-Bus signal q.forbid_events(forbidden) sync_dbus(bus, q, conn) q.unforbid_events(forbidden) # send good reply send_disco_reply( stream, event.stanza, some_identities, jingle_av_features, fake_client_dataforms) # we can now do audio calls cc, = q.expect_many( EventPattern('dbus-signal', signal='ContactCapabilitiesChanged'), ) assert_rccs_callable(cc.args[0][contact_handle]) assertEquals(cc.args[0], get_contacts_capabilities_sync(conn, [contact_handle]))
def test_hash(q, bus, conn, stream, contact, contact_handle, client): global caps_changed_flag presence = make_presence(contact, status='hello') stream.send(presence) q.expect('dbus-signal', signal='PresencesChanged', args=[{contact_handle: (2, u'available', 'hello')}]) # no special capabilities basic_caps = [(contact_handle, cs.CHANNEL_TYPE_TEXT, 3, 0)] assert conn.Capabilities.GetCapabilities([contact_handle]) == basic_caps # send updated presence with Jingle caps info presence = make_presence(contact, status='hello', caps={'node': client, 'ver': '0.1', }) stream.send(presence) # Gabble looks up our capabilities event = q.expect('stream-iq', to=contact, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + '0.1' # send good reply send_disco_reply(stream, event.stanza, [], jingle_av_features) # we can now do audio calls event = q.expect('dbus-signal', signal='CapabilitiesChanged') caps_diff = event.args[0] media_diff = [c for c in caps_diff if c[1] == cs.CHANNEL_TYPE_STREAMED_MEDIA][0] assert media_diff[5] & cs.MEDIA_CAP_AUDIO, media_diff[5] caps_changed_flag = False # Send presence without any capabilities. XEP-0115 §8.4 Caps Optimization # says “receivers of presence notifications MUST NOT expect an annotation # on every presence notification they receive”, so the contact should still # be media-capable afterwards. stream.send(make_presence(contact, status='very capable')) q.expect('dbus-signal', signal='PresencesChanged', args=[{contact_handle: (2, u'available', 'very capable')}]) ye_olde_caps = conn.Capabilities.GetCapabilities([contact_handle]) assertLength(1, [c for c in ye_olde_caps if c[1] == cs.CHANNEL_TYPE_STREAMED_MEDIA and c[3] & cs.MEDIA_CAP_AUDIO]) # send bogus presence caps = { 'node': client, 'ver': 'ceci=nest=pas=un=hash', 'hash': 'sha-1', } presence = make_presence(contact, status='hello', caps=caps) stream.send(presence) # Gabble looks up our capabilities event = q.expect('stream-iq', to=contact, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + caps['ver'] # send bogus reply send_disco_reply(stream, event.stanza, [], ['http://jabber.org/protocol/bogus-feature']) # don't receive any D-Bus signal sync_dbus(bus, q, conn) sync_stream(q, stream) assert caps_changed_flag == False # send presence with empty caps presence = make_presence(contact, status='hello', caps={'node': client, 'ver': '0.0', }) stream.send(presence) # Gabble looks up our capabilities event = q.expect('stream-iq', to=contact, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + '0.0' # still don't receive any D-Bus signal sync_dbus(bus, q, conn) assert caps_changed_flag == False # send good reply result = make_result_iq(stream, event.stanza) query = result.firstChildElement() stream.send(result) # we can now do nothing event = q.expect('dbus-signal', signal='CapabilitiesChanged') assert caps_changed_flag == True caps_changed_flag = False # send correct presence ver = compute_caps_hash(some_identities, jingle_av_features, fake_client_dataforms) caps = { 'node': client, 'ver': ver, 'hash': 'sha-1', } presence = make_presence(contact, status='hello', caps=caps) stream.send(presence) # Gabble looks up our capabilities event = q.expect('stream-iq', to=contact, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + caps['ver'] # don't receive any D-Bus signal sync_dbus(bus, q, conn) assert caps_changed_flag == False # send good reply send_disco_reply( stream, event.stanza, some_identities, jingle_av_features, fake_client_dataforms) # we can now do audio calls event = q.expect('dbus-signal', signal='CapabilitiesChanged', ) assert caps_changed_flag == True caps_changed_flag = False
def test(q, bus, conn, stream): client = 'http://example.com/perverse-client' contact_bare_jid = '*****@*****.**' contact_with_resource = '[email protected]/hi' contact_handle = conn.RequestHandles(cs.HT_CONTACT, [contact_bare_jid])[0] # Gabble gets a presence stanza from a bare JID, which is a tad surprising. features = [ ns.JINGLE_015, ns.JINGLE_015_AUDIO, ns.JINGLE_015_VIDEO, ns.GOOGLE_P2P, ] caps = {'node': client, 'hash': 'sha-1', 'ver': compute_caps_hash([], features, {}), } p = make_presence(contact_bare_jid, status='Hello', caps=caps) stream.send(p) # Gabble looks up the hash event = q.expect('stream-iq', to=contact_bare_jid, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assertEquals(client + '#' + caps['ver'], query_node.attributes['node']) # The bare jid replies send_disco_reply(stream, event.stanza, [], features) # Gabble lets us know their caps have changed. (Gabble used to ignore the # reply.) streamed_media_caps = (contact_handle, cs.CHANNEL_TYPE_STREAMED_MEDIA, 0, 3, 0, cs.MEDIA_CAP_AUDIO | cs.MEDIA_CAP_VIDEO) e = q.expect('dbus-signal', signal='CapabilitiesChanged') assertContains(streamed_media_caps, e.args[0]) # Gabble gets another presence stanza from the bare JID, with different # caps. features.append(ns.TUBES) caps = {'node': client, 'hash': 'sha-1', 'ver': compute_caps_hash([], features, {}), } p = make_presence(contact_bare_jid, status='Get out the abacus', caps=caps) stream.send(p) # Gabble looks up the new hash disco2 = q.expect('stream-iq', to=contact_bare_jid, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', disco2.stanza)[0] assertEquals(client + '#' + caps['ver'], query_node.attributes['node']) # This time, before the bare JID replies, Gabble gets a presence from the # resourceful jid. features_ = features + [ns.CHAT_STATES] caps = {'node': client, 'hash': 'sha-1', 'ver': compute_caps_hash([], features_, {}), } p = make_presence(contact_with_resource, status='Count this', caps=caps) stream.send(p) # Gabble throws away presence from the bare JID when it gets presence from # a resource (and vice versa), so it should now say the contact is # incapable. Gabble also looks up the resourceful JID's hash. cc, disco3 = q.expect_many( EventPattern('dbus-signal', signal='CapabilitiesChanged'), EventPattern('stream-iq', to=contact_with_resource, query_ns='http://jabber.org/protocol/disco#info'), ) assertDoesNotContain(streamed_media_caps, cc.args[0]) query_node = xpath.queryForNodes('/iq/query', disco3.stanza)[0] assertEquals(client + '#' + caps['ver'], query_node.attributes['node']) # The bare jid replies! Getting a disco reply from a bare JID when we've # got presence from resources used to crash Gabble, but now it just ignores # it. send_disco_reply(stream, disco2.stanza, [], features) # Now the resourceful JID replies: send_disco_reply(stream, disco3.stanza, [], features_) # Gabble should announce that the contact has acquired some caps. e = q.expect('dbus-signal', signal='CapabilitiesChanged') assertContains(streamed_media_caps, e.args[0])
def test(q, bus, conn, stream): bob = conn.get_contact_handle_sync('*****@*****.**') presence = make_presence('[email protected]/Foo', status='hello') stream.send(presence) q.expect('dbus-signal', signal='PresencesChanged', args=[{ bob: (cs.PRESENCE_AVAILABLE, u'available', 'hello') }]) basic_caps = [(bob, cs.CHANNEL_TYPE_TEXT, 3, 0)] # only Text for rcc in get_contacts_capabilities_sync(conn, [bob])[bob]: assertEquals(cs.CHANNEL_TYPE_TEXT, rcc[0].get(cs.CHANNEL_TYPE)) # holding the handle here: see below assertEquals( { bob: { cs.ATTR_CONTACT_CAPABILITIES: get_contacts_capabilities_sync(conn, [bob])[bob], cs.CONN + '/contact-id': '*****@*****.**', }, }, conn.Contacts.GetContactAttributes([bob], [cs.CONN_IFACE_CONTACT_CAPS], True)) # send updated presence with Jingle audio/video caps info. we turn on both # audio and video at the same time to test that all of the capabilities are # discovered before any capabilities change signal is emitted presence = make_presence( '[email protected]/Foo', status='hello', caps={ 'node': 'http://telepathy.freedesktop.org/fake-client', 'ver': '0.1', 'ext': 'video', }) stream.send(presence) # Gabble looks up both the version and the video bundles, in any order (version_event, video_event) = q.expect_many( EventPattern( 'stream-iq', to='[email protected]/Foo', query_ns='http://jabber.org/protocol/disco#info', query_node='http://telepathy.freedesktop.org/fake-client#0.1'), EventPattern( 'stream-iq', to='[email protected]/Foo', query_ns='http://jabber.org/protocol/disco#info', query_node='http://telepathy.freedesktop.org/fake-client#video')) # reply to the video bundle query first - this capability alone is not # sufficient to make us callable result = make_result_iq(stream, video_event.stanza) query = result.firstChildElement() feature = query.addElement('feature') feature['var'] = 'http://jabber.org/protocol/jingle/description/video' stream.send(result) # reply to the version bundle query, which should make us audio and # video callable result = make_result_iq(stream, version_event.stanza) query = result.firstChildElement() feature = query.addElement('feature') feature['var'] = 'http://jabber.org/protocol/jingle' feature = query.addElement('feature') feature['var'] = 'http://jabber.org/protocol/jingle/description/audio' feature = query.addElement('feature') feature['var'] = 'http://www.google.com/transport/p2p' stream.send(result) # we can now do audio and video calls cc, = q.expect_many( EventPattern( 'dbus-signal', signal='ContactCapabilitiesChanged', predicate=lambda e: check_rccs_callable(e.args[0][bob])), ) assert_rccs_callable(cc.args[0][bob], require_video=True, mutable_contents=True) assertEquals( { bob: { cs.ATTR_CONTACT_CAPABILITIES: cc.args[0][bob], cs.CONN + '/contact-id': '*****@*****.**', }, }, conn.Contacts.GetContactAttributes([bob], [cs.CONN_IFACE_CONTACT_CAPS], True)) # send updated presence without video support presence = make_presence( '[email protected]/Foo', status='hello', caps={ 'node': 'http://telepathy.freedesktop.org/fake-client', 'ver': '0.1', }) stream.send(presence) # we can now do only audio calls (and as a result have the ImmutableStreams # cap) cc, = q.expect_many( EventPattern('dbus-signal', signal='ContactCapabilitiesChanged'), ) assert_rccs_callable(cc.args[0][bob]) assert_rccs_not_callable(cc.args[0][bob], require_audio=False, require_video=True, mutable_contents=False) assertEquals( { bob: { cs.ATTR_CONTACT_CAPABILITIES: cc.args[0][bob], cs.CONN + '/contact-id': '*****@*****.**', }, }, conn.Contacts.GetContactAttributes([bob], [cs.CONN_IFACE_CONTACT_CAPS], True)) # go offline presence = make_presence('[email protected]/Foo', type='unavailable') stream.send(presence) # can't do audio calls any more q.expect_many( EventPattern('dbus-signal', signal='PresencesChanged', args=[{ bob: (cs.PRESENCE_OFFLINE, 'offline', '') }]), EventPattern('dbus-signal', signal='ContactCapabilitiesChanged'), ) # Contact went offline. Previously, this test asserted that the handle # became invalid, but that's not guaranteed to happen immediately; so we # now hold the handle (above), to guarantee that it does *not* become # invalid. rccs = get_contacts_capabilities_sync(conn, [bob])[bob] for rcc in rccs: assertEquals(cs.CHANNEL_TYPE_TEXT, rcc[0].get(cs.CHANNEL_TYPE)) assertEquals( { bob: { cs.ATTR_CONTACT_CAPABILITIES: rccs, cs.CONN + '/contact-id': '*****@*****.**', }, }, conn.Contacts.GetContactAttributes([bob], [cs.CONN_IFACE_CONTACT_CAPS], True)) # What about a handle that's not valid? assertEquals({}, conn.Contacts.GetContactAttributes( [31337], [cs.CONN_IFACE_CONTACT_CAPS], False))