def check_rccs_callable(rccs, require_audio=True, require_video=False, mutable_contents=None): """rccs: a list of RequestableChannelClass tuples""" audio_callable = False video_callable = False av_callable = False for rcc in rccs: fixed, allowed = rcc if fixed.get(cs.CHANNEL_TYPE) != cs.CHANNEL_TYPE_CALL: continue if fixed.get(cs.TARGET_HANDLE_TYPE) != cs.HT_CONTACT: continue if len(fixed) > (int(cs.CHANNEL_TYPE in fixed) + int(cs.TARGET_HANDLE_TYPE in fixed) + int(cs.CALL_INITIAL_AUDIO in fixed) + int(cs.CALL_INITIAL_VIDEO in fixed)): continue assert fixed.get(cs.CALL_INITIAL_AUDIO) in (True, None) assert fixed.get(cs.CALL_INITIAL_VIDEO) in (True, None) if mutable_contents is not None: if mutable_contents: assertContains(cs.CALL_MUTABLE_CONTENTS, allowed) else: assertDoesNotContain(cs.CALL_MUTABLE_CONTENTS, allowed) if (fixed.get(cs.CALL_INITIAL_AUDIO) == True or cs.CALL_INITIAL_AUDIO in allowed): audio_callable = True assertContains(cs.CALL_INITIAL_AUDIO_NAME, allowed) if (fixed.get(cs.CALL_INITIAL_VIDEO) == True or cs.CALL_INITIAL_VIDEO in allowed): video_callable = True assertContains(cs.CALL_INITIAL_VIDEO_NAME, allowed) if ((fixed.get(cs.CALL_INITIAL_AUDIO) == True or cs.CALL_INITIAL_AUDIO in allowed) and (fixed.get(cs.CALL_INITIAL_VIDEO) == True or cs.CALL_INITIAL_VIDEO in allowed)): av_callable = True if require_audio and not audio_callable: return False if require_video and not video_callable: return False if require_audio and require_video and not av_callable: return False return True
def test_create_invisible_list_failed(q, bus, conn, stream): conn.SimplePresence.SetPresence("away", "") conn.Connect() stream.handle_get_all_privacy_lists(q, bus, conn) get_list = q.expect('stream-iq', query_ns=ns.PRIVACY, iq_type='get') list_node = xpath.queryForNodes('//list', get_list.query)[0] assertEquals('invisible', list_node['name']) error = domish.Element((None, 'error')) error['type'] = 'cancel' error.addElement((ns.STANZA, 'item-not-found')) send_error_reply (stream, get_list.stanza, error) create_list = q.expect('stream-iq', query_ns=ns.PRIVACY, iq_type='set') list_node = xpath.queryForNodes('//list', create_list.query)[0] assertEquals('invisible', list_node['name']) assertNotEquals([], xpath.queryForNodes('/query/list/item/presence-out', create_list.query)) send_error_reply(stream, create_list.stanza) q.expect('dbus-signal', signal='StatusChanged', args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]) assertDoesNotContain("hidden", conn.Properties.Get(cs.CONN_IFACE_SIMPLE_PRESENCE, "Statuses"))
def advertise_caps(q, conn, stream, filters, expected_features, unexpected_features, expected_caps): self_handle = conn.GetSelfHandle() ret_caps = conn.ContactCapabilities.UpdateCapabilities( [(cs.CLIENT + '.Foo', filters, [])]) # Expect Gabble to reply with the correct caps event, namespaces, _, signaled_caps = receive_presence_and_ask_caps(q, stream) assertSameElements(expected_caps, signaled_caps) assertContains(ns.TUBES, namespaces) for var in expected_features: assertContains(var, namespaces) for var in unexpected_features: assertDoesNotContain(var, namespaces) # Check our own caps caps = conn.ContactCapabilities.GetContactCapabilities([self_handle]) assertSameElements(expected_caps, caps) # check the Contacts interface give the same caps caps_via_contacts_iface = conn.Contacts.GetContactAttributes( [self_handle], [cs.CONN_IFACE_CONTACT_CAPS], False) \ [self_handle][cs.ATTR_CONTACT_CAPABILITIES] assertSameElements(caps[self_handle], caps_via_contacts_iface)
def test_deny_unblock_remove(q, bus, conn, stream, stored, deny): """ Test unblocking a contact, and, while that request is pending, deleting them. """ self_handle = conn.GetSelfHandle() # This contact was on our roster, blocked and subscribed, when we started. contact = '*****@*****.**' handle = conn.RequestHandles(cs.HT_CONTACT, [contact])[0] # They're blocked, and we have a bidi subscription, so they should be on # deny and stored. (We already checked this earlier, but we've been messing # with the roster so let's be sure the preconditions are okay...) assertContains(handle, deny.Properties.Get(cs.CHANNEL_IFACE_GROUP, "Members")) assertContains(handle, stored.Properties.Get(cs.CHANNEL_IFACE_GROUP, "Members")) # Unblock them. call_async(q, deny.Group, 'RemoveMembers', [handle], "") roster_event = q.expect('stream-iq', query_ns=ns.ROSTER) item = roster_event.query.firstChildElement() assertEquals(contact, item['jid']) assertDoesNotContain((ns.GOOGLE_ROSTER, 't'), item.attributes) # If we now remove them from stored, the edit shouldn't be sent until the # unblock event has had a reply. q.forbid_events(remove_events) call_async(q, stored.Group, 'RemoveMembers', [handle], "") # Make sure if the remove is sent prematurely, we catch it. sync_stream(q, stream) q.unforbid_events(remove_events) # So now we send a roster push and reply for the unblock request. stream.send(make_set_roster_iq(stream, 'test@localhost/Resource', contact, 'both', False, attrs={})) acknowledge_iq(stream, roster_event.stanza) # And on receiving the push and reply, Gabble should show them being # removed from deny, and send a remove. _, roster_event = q.expect_many( EventPattern('dbus-signal', signal='MembersChanged', args=['', [], [handle], [], [], self_handle, cs.GC_REASON_NONE], predicate=is_deny), remove_events[0], ) item = roster_event.query.firstChildElement() assertEquals(contact, item['jid']) stream.send(make_set_roster_iq(stream, 'test@localhost/Resource', contact, 'remove', False, attrs={})) acknowledge_iq(stream, roster_event.stanza) q.expect('dbus-signal', signal='MembersChanged', args=['', [], [handle], [], [], 0, cs.GC_REASON_NONE], predicate=is_stored)
def advertise_caps(q, conn, stream, filters, expected_features, unexpected_features, expected_caps): self_handle = conn.Properties.Get(cs.CONN, "SelfHandle") ret_caps = conn.ContactCapabilities.UpdateCapabilities([ (cs.CLIENT + '.Foo', filters, []) ]) # Expect Gabble to reply with the correct caps event, namespaces, _, signaled_caps = receive_presence_and_ask_caps( q, stream) assertSameElements(expected_caps, signaled_caps) assertContains(ns.TUBES, namespaces) for var in expected_features: assertContains(var, namespaces) for var in unexpected_features: assertDoesNotContain(var, namespaces) # Check our own caps caps = get_contacts_capabilities_sync(conn, [self_handle]) assertSameElements(expected_caps, caps) # check the Contacts interface give the same caps caps_via_contacts_iface = conn.Contacts.GetContactAttributes( [self_handle], [cs.CONN_IFACE_CONTACT_CAPS], False) \ [self_handle][cs.ATTR_CONTACT_CAPABILITIES] assertSameElements(caps[self_handle], caps_via_contacts_iface)
def test_invisible_on_connect_fail_no_list(q, bus, conn, stream): props = conn.Properties.GetAll(cs.CONN_IFACE_SIMPLE_PRESENCE) assertNotEquals({}, props['Statuses']) presence_event_pattern = EventPattern('stream-presence') q.forbid_events([presence_event_pattern]) conn.SimplePresence.SetPresence("hidden", "") conn.Connect() stream.handle_get_all_privacy_lists(q, bus, conn) get_list = q.expect('stream-iq', query_ns=ns.PRIVACY, iq_type='get') list_node = xpath.queryForNodes('//list', get_list.query)[0] assertEquals('invisible', list_node['name']) send_error_reply(stream, get_list.stanza) q.unforbid_events([presence_event_pattern]) # Darn! At least we should have our presence set to DND. q.expect_many( EventPattern('dbus-signal', signal='PresencesChanged', interface=cs.CONN_IFACE_SIMPLE_PRESENCE, args=[{1: (6, 'dnd', '')}]), EventPattern('dbus-signal', signal='StatusChanged', args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED])) # 'hidden' should not be an available status. assertDoesNotContain("hidden", conn.Properties.Get(cs.CONN_IFACE_SIMPLE_PRESENCE, "Statuses"))
def advertise_caps(q, bus, conn, stream, filters, expected_features, unexpected_features, expected_caps): # make sure nothing from a previous update is still running sync_dbus(bus, q, conn) self_handle = conn.Properties.Get(cs.CONN, "SelfHandle") ret_caps = conn.ContactCapabilities.UpdateCapabilities( [(cs.CLIENT + '.Foo', filters, [])]) # Expect Gabble to reply with the correct caps event, namespaces, _, signaled_caps = receive_presence_and_ask_caps(q, stream) assertSameElements(expected_caps, signaled_caps[self_handle]) assertContains(ns.TP_FT_METADATA, namespaces) for var in expected_features: assertContains(var, namespaces) for var in unexpected_features: assertDoesNotContain(var, namespaces) # Check our own caps caps = get_contacts_capabilities_sync(conn, [self_handle]) assertSameElements(expected_caps, caps[self_handle]) # check the Contacts interface give the same caps caps_via_contacts_iface = conn.Contacts.GetContactAttributes( [self_handle], [cs.CONN_IFACE_CONTACT_CAPS], False) \ [self_handle][cs.ATTR_CONTACT_CAPABILITIES] assertSameElements(caps[self_handle], caps_via_contacts_iface)
def test_create_invisible_list_failed(q, bus, conn, stream): conn.SimplePresence.SetPresence("away", "") conn.Connect() stream.handle_get_all_privacy_lists(q, bus, conn) get_list = q.expect('stream-iq', query_ns=ns.PRIVACY, iq_type='get') list_node = xpath.queryForNodes('//list', get_list.query)[0] assertEquals('invisible', list_node['name']) error = domish.Element((None, 'error')) error['type'] = 'cancel' error.addElement((ns.STANZA, 'item-not-found')) send_error_reply(stream, get_list.stanza, error) create_list = q.expect('stream-iq', query_ns=ns.PRIVACY, iq_type='set') list_node = xpath.queryForNodes('//list', create_list.query)[0] assertEquals('invisible', list_node['name']) assertNotEquals([], xpath.queryForNodes('/query/list/item/presence-out', create_list.query)) send_error_reply(stream, create_list.stanza) q.expect('dbus-signal', signal='StatusChanged', args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]) assertDoesNotContain( "hidden", conn.Properties.Get(cs.CONN_IFACE_SIMPLE_PRESENCE, "Statuses"))
def reject_start_receiving(self, content): self.stop_receiving(content) content.stream.RequestReceiving(self.remote_handle, True) self.q.expect('dbus-signal', signal='ReceivingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START], path=content.stream.__dbus_object_path__), content.stream.Media.CompleteReceivingStateChange( cs.CALL_STREAM_FLOW_STATE_STARTED) o = self.q.expect_many( EventPattern('dbus-signal', signal='ReceivingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_STARTED], path=content.stream.__dbus_object_path__), EventPattern('dbus-signal', signal='RemoteMembersChanged', path=content.stream.__dbus_object_path__), EventPattern('sip-invite')) assertLength(0, o[1].args[2]) assertLength(1, o[1].args[0]) assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND, o[1].args[0][self.remote_handle]) assertEquals(self.self_handle, o[1].args[3][0]) assertEquals(cs.CALL_STATE_CHANGE_REASON_USER_REQUESTED, o[1].args[3][1]) reinvite_event = o[2] assertDoesNotContain('a=sendonly', reinvite_event.sip_message.body) assertDoesNotContain('a=inactive', reinvite_event.sip_message.body) self.context.check_call_sdp(reinvite_event.sip_message.body) body = reinvite_event.sip_message.body + 'a=recvonly\r\r' self.context.accept(reinvite_event.sip_message, body) ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0] self.q.expect_many(EventPattern('sip-ack', cseq=ack_cseq)) # Now let's restart receiving for real self.receiving = True self.context.reinvite() acc, rmb = self.q.expect_many( EventPattern('sip-response', code=200), EventPattern('dbus-signal', signal='RemoteMembersChanged', path=content.stream.__dbus_object_path__, predicate=lambda e: e.args[0] == {self.remote_handle: cs.CALL_SENDING_STATE_SENDING})) self.context.check_call_sdp(acc.sip_message.body, self.medias) self.context.ack(acc.sip_message)
def test_deny_unblock_remove(q, bus, conn, stream): """ Test unblocking a contact, and, while that request is pending, deleting them. """ # This contact was on our roster, blocked and subscribed, when we started. contact = '*****@*****.**' handle = conn.get_contact_handle_sync(contact) check_contact_roster(conn, contact, [], cs.SUBSCRIPTION_STATE_YES, cs.SUBSCRIPTION_STATE_YES) # They're blocked, and we have a bidi subscription, so they should be on # deny and stored. (We already checked this earlier, but we've been messing # with the roster so let's be sure the preconditions are okay...) assertContains(handle, conn.ContactBlocking.RequestBlockedContacts().keys()) # Unblock them. call_async(q, conn.ContactBlocking, 'UnblockContacts', [handle]) roster_event = q.expect('stream-iq', query_ns=ns.ROSTER) item = roster_event.query.firstChildElement() assertEquals(contact, item['jid']) assertDoesNotContain((ns.GOOGLE_ROSTER, 't'), item.attributes) # If we now remove them from stored, the edit shouldn't be sent until the # unblock event has had a reply. q.forbid_events(remove_events) call_async(q, conn.ContactList, 'RemoveContacts', [handle]) # Make sure if the remove is sent prematurely, we catch it. sync_stream(q, stream) q.unforbid_events(remove_events) # So now we send a roster push and reply for the unblock request. stream.send(make_set_roster_iq(stream, 'test@localhost/Resource', contact, 'both', False, attrs={})) acknowledge_iq(stream, roster_event.stanza) # And on receiving the push and reply, Gabble should show them being # removed from deny, and send a remove. _, roster_event = q.expect_many( EventPattern('dbus-signal', signal='BlockedContactsChanged', predicate=lambda e: blocked_contacts_changed_predicate(e, [], [contact])), remove_events[0], ) item = roster_event.query.firstChildElement() assertEquals(contact, item['jid']) stream.send(make_set_roster_iq(stream, 'test@localhost/Resource', contact, 'remove', False, attrs={})) acknowledge_iq(stream, roster_event.stanza)
def reject_start_receiving(self, content): self.stop_receiving(content) content.stream.RequestReceiving(self.remote_handle, True) self.q.expect('dbus-signal', signal='ReceivingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START], path=content.stream.__dbus_object_path__), content.stream.Media.CompleteReceivingStateChange( cs.CALL_STREAM_FLOW_STATE_STARTED) o = self.q.expect_many( EventPattern('dbus-signal', signal='ReceivingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_STARTED], path=content.stream.__dbus_object_path__), EventPattern('dbus-signal', signal='RemoteMembersChanged', path=content.stream.__dbus_object_path__), EventPattern('sip-invite')) assertLength(0, o[1].args[2]) assertLength(1, o[1].args[0]) assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND, o[1].args[0][self.remote_handle]) assertEquals(self.self_handle, o[1].args[3][0]) assertEquals(cs.CALL_STATE_CHANGE_REASON_USER_REQUESTED, o[1].args[3][1]) reinvite_event = o[2] assertDoesNotContain('a=sendonly', reinvite_event.sip_message.body) assertDoesNotContain('a=inactive', reinvite_event.sip_message.body) self.context.check_call_sdp(reinvite_event.sip_message.body) body = reinvite_event.sip_message.body + 'a=recvonly\r\r' self.context.accept(reinvite_event.sip_message, body) ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0] self.q.expect_many( EventPattern('sip-ack', cseq=ack_cseq)) # Now let's restart receiving for real self.receiving = True self.context.reinvite() acc , rmb = self.q.expect_many( EventPattern('sip-response', code=200), EventPattern('dbus-signal', signal='RemoteMembersChanged', path=content.stream.__dbus_object_path__, predicate=lambda e: e.args[0] == {self.remote_handle: cs.CALL_SENDING_STATE_SENDING})) self.context.check_call_sdp(acc.sip_message.body, self.medias) self.context.ack(acc.sip_message)
def check_caps(namespaces, desired): """Assert that all the FIXED_CAPS are supported, and of the VARIABLE_CAPS, every capability in desired is supported, and every other capability is not. """ for c in FIXED_CAPS: assertContains(c, namespaces) for c in VARIABLE_CAPS: if c in desired: assertContains(c, namespaces) else: assertDoesNotContain(c, namespaces)
def start_receiving(self, content, already_receiving=False): self.receiving = True content.stream.RequestReceiving(self.remote_handle, True) self.q.expect('dbus-signal', signal='ReceivingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START], path=content.stream.__dbus_object_path__), content.stream.Media.CompleteReceivingStateChange( cs.CALL_STREAM_FLOW_STATE_STARTED) o = self.q.expect_many( EventPattern('dbus-signal', signal='ReceivingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_STARTED], path=content.stream.__dbus_object_path__), EventPattern('dbus-signal', signal='RemoteMembersChanged', path=content.stream.__dbus_object_path__), EventPattern('sip-invite')) assertLength(0, o[1].args[2]) assertLength(1, o[1].args[0]) assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND, o[1].args[0][self.remote_handle]) assertEquals(self.self_handle, o[1].args[3][0]) assertEquals(cs.CALL_STATE_CHANGE_REASON_USER_REQUESTED, o[1].args[3][1]) reinvite_event = o[2] assertDoesNotContain('a=sendonly', reinvite_event.sip_message.body) assertDoesNotContain('a=inactive', reinvite_event.sip_message.body) self.context.check_call_sdp(reinvite_event.sip_message.body) self.context.accept(reinvite_event.sip_message) ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0] o = self.q.expect_many( EventPattern('sip-ack', cseq=ack_cseq), EventPattern('dbus-signal', signal='RemoteMembersChanged', path=content.stream.__dbus_object_path__)) assertLength(1, o[1].args[0]) assertLength(0, o[1].args[2]) assertEquals(cs.CALL_SENDING_STATE_SENDING, o[1].args[0][self.remote_handle])
def advertise_caps(q, bus, conn, service, filters, expected_features, unexpected_features, expected_caps): # make sure nothing from a previous update is still running sync_dbus(bus, q, conn) self_handle = conn.Properties.Get(cs.CONN, "SelfHandle") self_handle_name = conn.Properties.Get(cs.CONN, "SelfID") ret_caps = conn.ContactCapabilities.UpdateCapabilities([ (cs.CLIENT + '.Foo', filters, []) ]) presence, event_dbus = q.expect_many( EventPattern('service-resolved', service=service), EventPattern('dbus-signal', signal='ContactCapabilitiesChanged')) assertLength(1, event_dbus.args) signaled_caps = event_dbus.args[0] outbound = connect_to_stream(q, 'test@foobar', self_handle_name, str(presence.pt), presence.port) e = q.expect('connection-result') assert e.succeeded, e.reason e = q.expect('stream-opened', connection=outbound) # Expect Salut to reply with the correct caps event, namespaces = disco_caps(q, outbound, presence.txt) assertSameElements(expected_caps, signaled_caps[self_handle]) assertContains(ns.TP_FT_METADATA, namespaces) for var in expected_features: assertContains(var, namespaces) for var in unexpected_features: assertDoesNotContain(var, namespaces) # check the Contacts interface give the same caps caps_via_contacts_iface = conn.Contacts.GetContactAttributes( [self_handle], [cs.CONN_IFACE_CONTACT_CAPS], False) \ [self_handle][cs.ATTR_CONTACT_CAPABILITIES] assertSameElements(expected_caps, caps_via_contacts_iface) # close things... outbound.send('</stream:stream>') sync_dbus(bus, q, conn) outbound.transport.loseConnection()
def advertise_caps(q, bus, conn, service, filters, expected_features, unexpected_features, expected_caps): # make sure nothing from a previous update is still running sync_dbus(bus, q, conn) self_handle = conn.Properties.Get(cs.CONN, "SelfHandle") self_handle_name = conn.Properties.Get(cs.CONN, "SelfID") ret_caps = conn.ContactCapabilities.UpdateCapabilities( [(cs.CLIENT + '.Foo', filters, [])]) presence, event_dbus = q.expect_many( EventPattern('service-resolved', service=service), EventPattern('dbus-signal', signal='ContactCapabilitiesChanged') ) assertLength(1, event_dbus.args) signaled_caps = event_dbus.args[0] outbound = connect_to_stream(q, 'test@foobar', self_handle_name, str(presence.pt), presence.port) e = q.expect('connection-result') assert e.succeeded, e.reason e = q.expect('stream-opened', connection=outbound) # Expect Salut to reply with the correct caps event, namespaces = disco_caps(q, outbound, presence.txt) assertSameElements(expected_caps, signaled_caps[self_handle]) assertContains(ns.TP_FT_METADATA, namespaces) for var in expected_features: assertContains(var, namespaces) for var in unexpected_features: assertDoesNotContain(var, namespaces) # check the Contacts interface give the same caps caps_via_contacts_iface = conn.Contacts.GetContactAttributes( [self_handle], [cs.CONN_IFACE_CONTACT_CAPS], False) \ [self_handle][cs.ATTR_CONTACT_CAPABILITIES] assertSameElements(expected_caps, caps_via_contacts_iface) # close things... outbound.send('</stream:stream>') sync_dbus(bus, q, conn) outbound.transport.loseConnection()
def test_google_caps(q, bus, conn, stream): i = 1 # we want to make sure all permutations of voice-v1 and video-v1 # result in the correct caps, so let's do exactly that. for j in (1, 2): for ext_set in permutations(['voice-v1', 'video-v1'], j): jid = 'larry%s@page/mountainview' % i i += 1 # order of these ext values shouldn't matter gcaps = { 'node': 'blahblahthiskeepsonchanging', 'ver': '1.1', 'ext': ' '.join(ext_set) } handle = conn.get_contact_handle_sync(jid) send_presence(q, conn, stream, jid, gcaps, initial=True) e = q.expect('dbus-signal', signal='ContactCapabilitiesChanged', predicate=lambda e: handle in e.args[0]) assertEquals(1, len(e.args[0])) rccs = e.args[0][handle] found = False for fixed, allowed in rccs: if fixed[cs.CHANNEL_TYPE] != cs.CHANNEL_TYPE_CALL: continue # we should only have InitialAudio or InitialVideo if # voice-v1 or video-v1 is present respectively for a, b in [('voice-v1' in ext_set, cs.CALL_INITIAL_AUDIO), ('video-v1' in ext_set, cs.CALL_INITIAL_VIDEO)]: if a: assertContains(b, allowed) else: assertDoesNotContain(b, allowed) found = True assert found
def extract_disco_parts(stanza): identity_nodes = xpath.queryForNodes('/iq/query/identity', stanza) assertLength(1, identity_nodes) identity_node = identity_nodes[0] assertEquals('client', identity_node['category']) assertEquals(config.CLIENT_TYPE, identity_node['type']) assertEquals(config.PACKAGE_STRING, identity_node['name']) assertDoesNotContain('xml:lang', identity_node.attributes) identity = 'client/%s//%s' % (config.CLIENT_TYPE, config.PACKAGE_STRING) features = [] for feature in xpath.queryForNodes('/iq/query/feature', stanza): features.append(feature['var']) # a quick and ugly data form extractor x_nodes = xpath.queryForNodes('/iq/query/x', stanza) or [] dataforms = extract_data_forms(x_nodes) return ([identity], features, dataforms)
def unhold_succeed(self): self.chan.Hold.RequestHold(False) events = self.stream_dbus_signal_event ( 'ReceivingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START]) events += self.stream_dbus_signal_event( 'SendingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START]) o = self.q.expect_many( EventPattern('dbus-signal', signal='HoldStateChanged', args=[cs.HS_PENDING_UNHOLD, cs.HSR_REQUESTED]), *events) for c in self.contents: c.stream.Media.CompleteReceivingStateChange( cs.CALL_STREAM_FLOW_STATE_STARTED) c.stream.Media.CompleteSendingStateChange( cs.CALL_STREAM_FLOW_STATE_STARTED) events = self.stream_dbus_signal_event ( 'ReceivingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_STARTED]) events += self.stream_dbus_signal_event( 'SendingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_STARTED]) o = self.q.expect_many( EventPattern('sip-invite'), EventPattern('dbus-signal', signal='HoldStateChanged', args=[cs.HS_UNHELD, cs.HSR_REQUESTED]), *events) reinvite_event = o[0] medias = map(lambda x: (x[0], None), self.medias) assertDoesNotContain('a=sendonly', reinvite_event.sip_message.body) assertDoesNotContain('a=inactive', reinvite_event.sip_message.body) self.context.check_call_sdp(reinvite_event.sip_message.body, medias) self.context.accept(reinvite_event.sip_message) ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0] self.q.expect('sip-ack', cseq=ack_cseq)
def unhold_succeed(self): self.chan.Hold.RequestHold(False) events = self.stream_dbus_signal_event( 'ReceivingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START]) events += self.stream_dbus_signal_event( 'SendingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START]) o = self.q.expect_many( EventPattern('dbus-signal', signal='HoldStateChanged', args=[cs.HS_PENDING_UNHOLD, cs.HSR_REQUESTED]), *events) for c in self.contents: c.stream.Media.CompleteReceivingStateChange( cs.CALL_STREAM_FLOW_STATE_STARTED) c.stream.Media.CompleteSendingStateChange( cs.CALL_STREAM_FLOW_STATE_STARTED) events = self.stream_dbus_signal_event( 'ReceivingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_STARTED]) events += self.stream_dbus_signal_event( 'SendingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_STARTED]) o = self.q.expect_many( EventPattern('sip-invite'), EventPattern('dbus-signal', signal='HoldStateChanged', args=[cs.HS_UNHELD, cs.HSR_REQUESTED]), *events) reinvite_event = o[0] medias = map(lambda x: (x[0], None), self.medias) assertDoesNotContain('a=sendonly', reinvite_event.sip_message.body) assertDoesNotContain('a=inactive', reinvite_event.sip_message.body) self.context.check_call_sdp(reinvite_event.sip_message.body, medias) self.context.accept(reinvite_event.sip_message) ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0] self.q.expect('sip-ack', cseq=ack_cseq)
def disco_caps(q, stream, presence): c_nodes = xpath.queryForNodes('/presence/c', presence.stanza) assert c_nodes is not None assertLength(1, c_nodes) hash = c_nodes[0].attributes['hash'] ver = c_nodes[0].attributes['ver'] node = c_nodes[0].attributes['node'] assertEquals('sha-1', hash) # ask caps request = \ elem_iq(stream, 'get', from_='[email protected]/resource')( elem(ns.DISCO_INFO, 'query', node=(node + '#' + ver)) ) stream.send(request) # receive caps event = q.expect('stream-iq', query_ns=ns.DISCO_INFO, iq_id=request['id']) # Check that Gabble's announcing the identity we think it should be. identity_nodes = xpath.queryForNodes('/iq/query/identity', event.stanza) assertLength(1, identity_nodes) identity_node = identity_nodes[0] assertEquals('client', identity_node['category']) assertEquals(config.CLIENT_TYPE, identity_node['type']) assertEquals(config.PACKAGE_STRING, identity_node['name']) assertDoesNotContain('xml:lang', identity_node.attributes) identity = 'client/%s//%s' % (config.CLIENT_TYPE, config.PACKAGE_STRING) features = [] for feature in xpath.queryForNodes('/iq/query/feature', event.stanza): features.append(feature['var']) # Check if the hash matches the announced capabilities assertEquals(compute_caps_hash([identity], features, {}), ver) return (event, features)
def test_invisible_on_connect_fail_no_list(q, bus, conn, stream): props = conn.Properties.GetAll(cs.CONN_IFACE_SIMPLE_PRESENCE) assertNotEquals({}, props['Statuses']) presence_event_pattern = EventPattern('stream-presence') q.forbid_events([presence_event_pattern]) conn.SimplePresence.SetPresence("hidden", "") conn.Connect() stream.handle_get_all_privacy_lists(q, bus, conn) get_list = q.expect('stream-iq', query_ns=ns.PRIVACY, iq_type='get') list_node = xpath.queryForNodes('//list', get_list.query)[0] assertEquals('invisible', list_node['name']) send_error_reply(stream, get_list.stanza) q.unforbid_events([presence_event_pattern]) # Darn! At least we should have our presence set to DND. q.expect_many( EventPattern('dbus-signal', signal='PresencesChanged', interface=cs.CONN_IFACE_SIMPLE_PRESENCE, args=[{ 1: (6, 'dnd', '') }]), EventPattern('dbus-signal', signal='StatusChanged', args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED])) # 'hidden' should not be an available status. assertDoesNotContain( "hidden", conn.Properties.Get(cs.CONN_IFACE_SIMPLE_PRESENCE, "Statuses"))
def disco_caps(q, stream, txt): hash = txt_get_key(txt, 'hash') ver = txt_get_key(txt, 'ver') node = txt_get_key(txt, 'node') assertEquals('sha-1', hash) # ask caps request = \ elem_iq(stream, 'get', from_='fake_contact@nearby')( elem(ns.DISCO_INFO, 'query', node=(node + '#' + ver)) ) stream.send(request) # receive caps event = q.expect('stream-iq', query_ns=ns.DISCO_INFO, iq_id=request['id']) # Check that Gabble's announcing the identity we think it should be. identity_nodes = xpath.queryForNodes('/iq/query/identity', event.stanza) assertLength(1, identity_nodes) identity_node = identity_nodes[0] assertEquals('client', identity_node['category']) assertEquals('pc', identity_node['type']) assertEquals(config.PACKAGE_STRING, identity_node['name']) assertDoesNotContain('xml:lang', identity_node.attributes) identity = 'client/%s//%s' % ('pc', config.PACKAGE_STRING) features = [] for feature in xpath.queryForNodes('/iq/query/feature', event.stanza): features.append(feature['var']) # Check if the hash matches the announced capabilities assertEquals(compute_caps_hash([identity], features, {}), ver) return (event, features)
def start_sending(self, content): content.stream.SetSending(True) self.sending = True reinvite_event, lss = self.q.expect_many( EventPattern('sip-invite'), EventPattern('dbus-signal', signal='LocalSendingStateChanged', path=content.stream.__dbus_object_path__)) assertEquals(cs.CALL_SENDING_STATE_SENDING, lss.args[0]) assertEquals(self.self_handle, lss.args[1][0]) assertDoesNotContain('a=sendonly', reinvite_event.sip_message.body) if self.receiving: assertDoesNotContain('a=inactive', reinvite_event.sip_message.body) assertDoesNotContain('a=recvonly', reinvite_event.sip_message.body) else: self.context.check_call_sdp(reinvite_event.sip_message.body, [('audio','recvonly')]) self.context.check_call_sdp(reinvite_event.sip_message.body) self.context.accept(reinvite_event.sip_message) ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0] self.q.expect_many( EventPattern('sip-ack', cseq=ack_cseq), EventPattern('dbus-signal', signal='SendingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START])) content.stream.Media.CompleteSendingStateChange( cs.CALL_STREAM_FLOW_STATE_STARTED) self.q.expect('dbus-signal', signal='SendingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_STARTED], path=content.stream.__dbus_object_path__)
def start_sending(self, content): content.stream.SetSending(True) self.sending = True reinvite_event, lss = self.q.expect_many( EventPattern('sip-invite'), EventPattern('dbus-signal', signal='LocalSendingStateChanged', path=content.stream.__dbus_object_path__)) assertEquals(cs.CALL_SENDING_STATE_SENDING, lss.args[0]) assertEquals(self.self_handle, lss.args[1][0]) assertDoesNotContain('a=sendonly', reinvite_event.sip_message.body) if self.receiving: assertDoesNotContain('a=inactive', reinvite_event.sip_message.body) assertDoesNotContain('a=recvonly', reinvite_event.sip_message.body) else: self.context.check_call_sdp(reinvite_event.sip_message.body, [('audio', 'recvonly')]) self.context.check_call_sdp(reinvite_event.sip_message.body) self.context.accept(reinvite_event.sip_message) ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0] self.q.expect_many( EventPattern('sip-ack', cseq=ack_cseq), EventPattern('dbus-signal', signal='SendingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START])) content.stream.Media.CompleteSendingStateChange( cs.CALL_STREAM_FLOW_STATE_STARTED) self.q.expect('dbus-signal', signal='SendingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_STARTED], path=content.stream.__dbus_object_path__)
def test(q, bus, conn, stream): # Ignore conn here, we're only dealing with the CM and Protocol objects. cm = bus.get_object(cs.CM + '.haze', tp_path_prefix + '/ConnectionManager/haze') cm_iface = dbus.Interface(cm, cs.CM) cm_props = dbus.Interface(cm, cs.PROPERTIES_IFACE) protocols = cm_props.Get(cs.CM, 'Protocols') protocol_names = cm_iface.ListProtocols() assertEquals(set(protocols.iterkeys()), set(protocol_names)) for name in protocol_names: props = protocols[name] protocol = bus.get_object( cm.bus_name, cm.object_path + '/' + name.replace('-', '_')) protocol_iface = dbus.Interface(protocol, cs.PROTOCOL) protocol_props = dbus.Interface(protocol, cs.PROPERTIES_IFACE) flat_props = protocol_props.GetAll(cs.PROTOCOL) protocol_avatar_props = protocol_props.GetAll( cs.PROTOCOL_IFACE_AVATARS) # Protocol is supposed to implement Interface.Avatars iff the # connection implements Avatars as well. if cs.CONN_IFACE_AVATARS in flat_props['ConnectionInterfaces']: assertContains(cs.PROTOCOL_IFACE_AVATARS, props[cs.PROTOCOL + '.Interfaces']) else: assertDoesNotContain(cs.PROTOCOL_IFACE_AVATARS, props[cs.PROTOCOL + '.Interfaces']) parameters = cm_iface.GetParameters(name) assertEquals(parameters, props[cs.PROTOCOL + '.Parameters']) assertEquals(parameters, flat_props['Parameters']) assertEquals(parameters, protocol_props.Get(cs.PROTOCOL, 'Parameters')) assertEquals(flat_props['VCardField'], props[cs.PROTOCOL + '.VCardField']) assertEquals(flat_props['Interfaces'], props[cs.PROTOCOL + '.Interfaces']) assertEquals(flat_props['EnglishName'], props[cs.PROTOCOL + '.EnglishName']) assertEquals(flat_props['Icon'], props[cs.PROTOCOL + '.Icon']) assertEquals(flat_props['ConnectionInterfaces'], props[cs.PROTOCOL + '.ConnectionInterfaces']) assertEquals(flat_props['RequestableChannelClasses'], props[cs.PROTOCOL + '.RequestableChannelClasses']) param_map = {} param_flags = {} param_type = {} param_def = {} for p in parameters: param_map[p[0]] = tuple(p[1:]) param_flags[p[0]] = p[1] param_type[p[0]] = p[2] param_def[p[0]] = p[3] # We use special cases to rename these; make sure they don't come back assertDoesNotContain(name, ('meanwhile', 'simple')) assertDoesNotContain('encoding', param_map) assertDoesNotContain('local_charset', param_map) if name not in ('local-xmpp', 'irc'): # it would be more correct for these protocols not to have this # parameter assertEquals((cs.PARAM_REQUIRED, 's', ''), param_map['account']) # a random selection of checks for known parameters... if name == 'gadugadu': assertEquals('x-gadugadu', flat_props['VCardField']) assertEquals('im-gadugadu', flat_props['Icon']) assertEquals((cs.PARAM_SECRET, 's', ''), param_map['password']) assertEquals('s', param_type['nick']) assertEquals('s', param_type['gg-server']) elif name == 'silc': assertEquals('x-silc', flat_props['VCardField']) assertEquals('im-silc', flat_props['Icon']) assertEquals((cs.PARAM_SECRET, 's', ''), param_map['password']) assertEquals('s', param_type['server']) elif name == 'irc': assertEquals('x-irc', flat_props['VCardField']) assertEquals('im-irc', flat_props['Icon']) assertEquals((cs.PARAM_SECRET, 's', ''), param_map['password']) assertEquals('s', param_type['charset']) assertEquals('s', param_type['username']) assertEquals('s', param_type['realname']) assertEquals('s', param_type['server']) assertEquals(cs.PARAM_HAS_DEFAULT, param_flags['server']) assertEquals( '*****@*****.**', protocol_iface.IdentifyAccount({ 'account': 'smcv', 'server': 'irc.debian.org' })) assertDoesNotContain(cs.CONN_IFACE_AVATARS, flat_props['ConnectionInterfaces']) assertDoesNotContain(cs.CONN_IFACE_CONTACT_BLOCKING, flat_props['ConnectionInterfaces']) assertDoesNotContain(cs.CONN_IFACE_MAIL_NOTIFICATION, flat_props['ConnectionInterfaces']) # Avatar not supported assertEquals(0, protocol_avatar_props['MaximumAvatarBytes']) assertEquals(0, protocol_avatar_props['MaximumAvatarHeight']) assertEquals(0, protocol_avatar_props['MaximumAvatarWidth']) assertEquals(0, protocol_avatar_props['MinimumAvatarHeight']) assertEquals(0, protocol_avatar_props['MinimumAvatarWidth']) assertEquals(0, protocol_avatar_props['RecommendedAvatarHeight']) assertEquals(0, protocol_avatar_props['RecommendedAvatarWidth']) assertEquals([], protocol_avatar_props['SupportedAvatarMIMETypes']) elif name == 'myspace': assertEquals('x-myspace', flat_props['VCardField']) assertEquals('im-myspace', flat_props['Icon']) assertEquals('s', param_type['server']) elif name == 'yahoo': assertEquals('x-yahoo', flat_props['VCardField']) assertEquals('im-yahoo', flat_props['Icon']) assertEquals('s', param_type['charset']) elif name == 'yahoojp': assertEquals('x-yahoo', flat_props['VCardField']) assertEquals('im-yahoojp', flat_props['Icon']) assertEquals('s', param_type['charset']) elif name == 'aim': assertEquals('x-aim', flat_props['VCardField']) assertEquals('im-aim', flat_props['Icon']) assertEquals('s', param_type['server']) elif name == 'msn': assertEquals('x-msn', flat_props['VCardField']) assertEquals('im-msn', flat_props['Icon']) assertEquals('s', param_type['server']) elif name == 'jabber': assertEquals('x-jabber', flat_props['VCardField']) assertEquals('im-jabber', flat_props['Icon']) assertDoesNotContain('require_tls', param_map) assertDoesNotContain('connect_server', param_map) assertEquals((cs.PARAM_SECRET, 's', ''), param_map['password']) assertEquals((cs.PARAM_HAS_DEFAULT, 'b', True), param_map['require-encryption']) assertEquals( '*****@*****.**', protocol_iface.IdentifyAccount({ 'account': '*****@*****.**', 'password': '******' })) assertEquals( r'*****@*****.**', protocol_iface.IdentifyAccount({ 'account': '*****@*****.**', 'server': r'corp.example.com', 'password': '******' })) # this contains an unsupported parameter call_async( q, protocol_iface, 'IdentifyAccount', { 'account': '*****@*****.**', 'embrace-and-extend': r'WORKGROUP\Bill', 'password': '******' }) q.expect('dbus-error', name=cs.INVALID_ARGUMENT) assertContains(cs.CONN_IFACE_AVATARS, flat_props['ConnectionInterfaces']) assertContains(cs.CONN_IFACE_CONTACT_BLOCKING, flat_props['ConnectionInterfaces']) assertContains(cs.CONN_IFACE_MAIL_NOTIFICATION, flat_props['ConnectionInterfaces']) # libpurple currently says there's no max size assertEquals(0, protocol_avatar_props['MaximumAvatarBytes']) assertEquals(96, protocol_avatar_props['MaximumAvatarHeight']) assertEquals(96, protocol_avatar_props['MaximumAvatarWidth']) assertEquals(32, protocol_avatar_props['MinimumAvatarHeight']) assertEquals(32, protocol_avatar_props['MinimumAvatarWidth']) assertEquals(0, protocol_avatar_props['RecommendedAvatarHeight']) assertEquals(0, protocol_avatar_props['RecommendedAvatarWidth']) assertEquals(['image/png'], protocol_avatar_props['SupportedAvatarMIMETypes']) elif name == 'qq': assertEquals('x-qq', flat_props['VCardField']) assertEquals('im-qq', flat_props['Icon']) elif name == 'sametime': assertEquals('x-sametime', flat_props['VCardField']) assertEquals('im-sametime', flat_props['Icon']) elif name == 'zephyr': assertEquals('x-zephyr', flat_props['VCardField']) assertEquals('im-zephyr', flat_props['Icon']) assertEquals('s', param_type['realm']) assertEquals('s', param_type['charset']) elif name == 'local-xmpp': # makes very little sense in an address book assertEquals('', flat_props['VCardField']) assertEquals('im-local-xmpp', flat_props['Icon']) assertEquals((cs.PARAM_REQUIRED, 's', ''), param_map['first-name']) assertDoesNotContain('first', param_map) assertEquals((cs.PARAM_REQUIRED, 's', ''), param_map['last-name']) assertDoesNotContain('last', param_map) assertEquals((0, 's', ''), param_map['email']) assertEquals((0, 's', ''), param_map['jid']) elif name == 'icq': assertEquals('x-icq', flat_props['VCardField']) assertEquals('im-icq', flat_props['Icon']) elif name == 'groupwise': assertEquals('x-groupwise', flat_props['VCardField']) assertEquals('im-groupwise', flat_props['Icon']) elif name == 'sipe': assertEquals('im-sipe', flat_props['Icon']) assertDoesNotContain('usersplit1', param_map) assertEquals((cs.PARAM_HAS_DEFAULT, 's', ''), param_map['login']) assertEquals( '[email protected],', protocol_iface.IdentifyAccount({ 'account': '*****@*****.**', 'password': '******' })) assertEquals( r'[email protected],WORKGROUP\Bill', protocol_iface.IdentifyAccount({ 'account': '*****@*****.**', 'login': r'WORKGROUP\Bill', 'password': '******' }))
def test(q, bus, conn, stream): event = q.expect('stream-iq', query_ns=ns.ROSTER) event.stanza['type'] = 'result' item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'both' item.addElement('group', content='women') item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'from' item.addElement('group', content='men') item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'to' item.addElement('group', content='men') stream.send(event.stanza) # Avoid relying on the implementation detail of exactly when # TpBaseContactList emits ContactsChanged, relative to when it # announces its channels. Prior to 0.20.3, 0.21.1 it would # announce the channels, emit GroupsChanged, then announce the channels # again... which was a bug, but it turned out this test relied on it. # # We do still rely on the implementation detail that we emit GroupsChanged # once per group with all of its members, not once per contact with all # of their groups. On a typical contact list, there are more contacts # than groups, so that'll work out smaller. q.expect_many( EventPattern( 'dbus-signal', signal='GroupsCreated', interface=cs.CONN_IFACE_CONTACT_GROUPS, path=conn.object_path, predicate=lambda e: groups_created_predicate(e, ['men', 'women'])), EventPattern('dbus-signal', signal='GroupsChanged', interface=cs.CONN_IFACE_CONTACT_GROUPS, path=conn.object_path, predicate=lambda e: groups_changed_predicate( e, conn, ['*****@*****.**'], ['women'], [])), EventPattern( 'dbus-signal', signal='GroupsChanged', interface=cs.CONN_IFACE_CONTACT_GROUPS, path=conn.object_path, predicate=lambda e: groups_changed_predicate( e, conn, ['*****@*****.**', '*****@*****.**'], ['men'], [])), ) amy, bob, che = conn.get_contact_handles_sync( ['*****@*****.**', '*****@*****.**', '*****@*****.**']) q.expect('dbus-signal', signal='ContactListStateChanged', args=[cs.CONTACT_LIST_STATE_SUCCESS]) # change Amy's groups call_async(q, conn.ContactGroups, 'SetContactGroups', amy, ['ladies', 'people starting with A']) s, iq = q.expect_many( EventPattern('dbus-signal', signal='GroupsCreated'), EventPattern('stream-iq', iq_type='set', query_name='query', query_ns=ns.ROSTER), ) assertEquals(set(('ladies', 'people starting with A')), set(s.args[0])) jid, groups = parse_roster_change_request(iq.query, iq.stanza) assertEquals('*****@*****.**', jid) assertEquals(set(('ladies', 'people starting with A')), groups) acknowledge_iq(stream, iq.stanza) q.expect('dbus-return', method='SetContactGroups') # Now the server sends us a roster push. send_roster_push(stream, '*****@*****.**', ['people starting with A', 'ladies']) # We get a single signal corresponding to that roster push e = q.expect('dbus-signal', signal='GroupsChanged', predicate=lambda e: e.args[0] == [amy]) assertEquals(set(['ladies', 'people starting with A']), set(e.args[1])) assertEquals(['women'], e.args[2]) # check that Amy's state is what we expected attrs = conn.Contacts.GetContactAttributes([amy], [cs.CONN_IFACE_CONTACT_GROUPS], False)[amy] # make the group list order-independent attrs[cs.CONN_IFACE_CONTACT_GROUPS + '/groups'] = \ set(attrs[cs.CONN_IFACE_CONTACT_GROUPS + '/groups']) assertEquals( { cs.CONN_IFACE_CONTACT_GROUPS + '/groups': set(['ladies', 'people starting with A']), cs.CONN + '/contact-id': '*****@*****.**' }, attrs) for it_worked in (False, True): # remove a group with a member (the old API couldn't do this) call_async(q, conn.ContactGroups, 'RemoveGroup', 'people starting with A') iq = q.expect('stream-iq', iq_type='set', query_name='query', query_ns=ns.ROSTER) jid, groups = parse_roster_change_request(iq.query, iq.stanza) assertEquals('*****@*****.**', jid) assertEquals(set(('ladies', )), groups) acknowledge_iq(stream, iq.stanza) # we emit these as soon as the IQ is ack'd, so that we can indicate # group removal... q.expect('dbus-signal', signal='GroupsRemoved', args=[['people starting with A']]) q.expect('dbus-signal', signal='GroupsChanged', args=[[amy], [], ['people starting with A']]) q.expect('dbus-return', method='RemoveGroup') if it_worked: # ... although in fact this is what *actually* removes Amy from the # group send_roster_push(stream, '*****@*****.**', ['ladies']) else: # if the change didn't "stick", this message will revert it send_roster_push(stream, '*****@*****.**', ['ladies', 'people starting with A']) q.expect('dbus-signal', signal='GroupsCreated', args=[['people starting with A']]) q.expect('dbus-signal', signal='GroupsChanged', args=[[amy], ['people starting with A'], []]) sync_dbus(bus, q, conn) sync_stream(q, stream) check_contact_roster(conn, '*****@*****.**', ['ladies', 'people starting with A']) # sanity check: after all that, we expect Amy to be in group 'ladies' only sync_dbus(bus, q, conn) sync_stream(q, stream) assertEquals( { cs.CONN_IFACE_CONTACT_GROUPS + '/groups': ['ladies'], cs.CONN + '/contact-id': '*****@*****.**' }, conn.Contacts.GetContactAttributes([amy], [cs.CONN_IFACE_CONTACT_GROUPS], False)[amy]) # Rename group 'ladies' to 'girls' call_async(q, conn.ContactGroups, 'RenameGroup', 'ladies', 'girls') # Amy is added to 'girls' e = q.expect('stream-iq', iq_type='set', query_name='query', query_ns=ns.ROSTER) jid, groups = parse_roster_change_request(e.query, e.stanza) assertEquals('*****@*****.**', jid) assertEquals(set(['girls', 'ladies']), groups) send_roster_push(stream, '*****@*****.**', ['girls', 'ladies']) acknowledge_iq(stream, e.stanza) # Amy is removed from 'ladies' e = q.expect('stream-iq', iq_type='set', query_name='query', query_ns=ns.ROSTER) jid, groups = parse_roster_change_request(e.query, e.stanza) assertEquals('*****@*****.**', jid) assertEquals(set(['girls']), groups) send_roster_push(stream, '*****@*****.**', ['girls']) acknowledge_iq(stream, e.stanza) q.expect('dbus-return', method='RenameGroup') # check everything has been updated groups = conn.Properties.Get(cs.CONN_IFACE_CONTACT_GROUPS, 'Groups') assertContains('girls', groups) assertDoesNotContain('ladies', groups) contacts = conn.ContactList.GetContactListAttributes( [cs.CONN_IFACE_CONTACT_GROUPS], False) assertEquals(['girls'], contacts[amy][cs.CONN_IFACE_CONTACT_GROUPS + '/groups'])
def test(q, bus, mc): simulated_cm = SimulatedConnectionManager(q, bus) ctl_dir = os.environ['MC_ACCOUNT_DIR'] old_key_file_name = os.path.join(ctl_dir, 'accounts.cfg') newer_key_file_name = os.path.join(os.environ['XDG_DATA_HOME'], 'telepathy', 'mission-control', 'accounts.cfg') # We do several scenarios in one MC run, to speed up testing a bit. scenarios = ('low', 'priority', 'masked', 'migration', 'absentcm') variant_file_names = {} low_prio_variant_file_names = {} account_paths = {} tails = {} for s in scenarios: variant_file_names[s] = os.path.join( os.environ['XDG_DATA_HOME'], 'telepathy', 'mission-control', 'fakecm-fakeprotocol-dontdivert%s_40example_2ecom0.account' % s) tails[s] = ('fakecm/fakeprotocol/dontdivert%s_40example_2ecom0' % s) account_paths[s] = cs.ACCOUNT_PATH_PREFIX + tails[s] low_prio_variant_file_names[s] = os.path.join( os.environ['XDG_DATA_DIRS'].split(':')[0], 'telepathy', 'mission-control', 'fakecm-fakeprotocol-dontdivert%s_40example_2ecom0.account' % s) try: os.makedirs(os.path.dirname(variant_file_names[s]), 0700) except OSError as e: if e.errno != errno.EEXIST: raise try: os.makedirs(os.path.dirname(low_prio_variant_file_names[s]), 0700) except OSError as e: if e.errno != errno.EEXIST: raise # This is deliberately a lower-priority location open(low_prio_variant_file_names['low'], 'w').write("""{ 'manager': <'fakecm'>, 'protocol': <'fakeprotocol'>, 'DisplayName': <'Account in a low-priority location'>, 'AutomaticPresence': <(uint32 2, 'available', '')>, 'Parameters': <{ 'account': <'*****@*****.**'>, 'password': <'password_in_variant_file'>, 'snakes': <uint32 42> }> } """) # This is in a lower-priority location and we don't know the # parameters' types yet open(low_prio_variant_file_names['migration'], 'w').write("""{ 'manager': <'fakecm'>, 'protocol': <'fakeprotocol'>, 'DisplayName': <'Account in a low-priority location with KeyFileParameters'>, 'AutomaticPresence': <(uint32 2, 'available', '')>, 'KeyFileParameters': <{ 'account': '*****@*****.**', 'password': '******', 'snakes': '42' }> } """) # This is in a lower-priority location, and we don't know the # parameters' types, and we can't learn them by asking the CM # because it isn't installed open(low_prio_variant_file_names['absentcm'], 'w').write("""{ 'manager': <'absentcm'>, 'protocol': <'absentprotocol'>, 'DisplayName': <'Account in a low-priority location with absent CM'>, 'AutomaticPresence': <(uint32 2, 'available', '')>, 'KeyFileParameters': <{ 'account': '*****@*****.**', 'password': '******', 'snakes': '42' }> } """) # This version of this account will be used open(variant_file_names['priority'], 'w').write("""{ 'manager': <'fakecm'>, 'protocol': <'fakeprotocol'>, 'DisplayName': <'Visible'>, 'AutomaticPresence': <(uint32 2, 'available', '')>, 'KeyFileParameters': <{'account': '*****@*****.**', 'password': '******', 'snakes': '42' }> } """) # This one won't, because it's "masked" by the higher-priority one open(low_prio_variant_file_names['priority'], 'w').write("""{ 'manager': <'fakecm'>, 'protocol': <'fakeprotocol'>, 'DisplayName': <'Hidden'>, 'Nickname': <'Hidden'>, 'AutomaticPresence': <(uint32 2, 'available', '')>, 'KeyFileParameters': <{'account': '*****@*****.**', 'password': '******', 'snakes': '42' }> } """) # This empty file is considered to "mask" the lower-priority one open(variant_file_names['masked'], 'w').write('') open(low_prio_variant_file_names['masked'], 'w').write("""{ 'manager': <'fakecm'>, 'protocol': <'fakeprotocol'>, 'AutomaticPresence': <(uint32 2, 'available', '')>, 'KeyFileParameters': <{'account': '*****@*****.**', 'password': '******', 'snakes': '42' }> } """) mc = MC(q, bus) account_manager, properties, interfaces = connect_to_mc(q, bus, mc) for s in scenarios: if s == 'masked': assertDoesNotContain(account_paths[s], properties['ValidAccounts']) assertDoesNotContain(account_paths[s], properties['InvalidAccounts']) elif s == 'absentcm': assertContains(account_paths[s], properties['InvalidAccounts']) assertDoesNotContain(account_paths[s], properties['ValidAccounts']) else: assertContains(account_paths[s], properties['ValidAccounts']) assertDoesNotContain(account_paths[s], properties['InvalidAccounts']) accounts = {} account_ifaces = {} for s in scenarios: if s != 'masked': accounts[s] = get_fakecm_account(bus, mc, account_paths[s]) account_ifaces[s] = dbus.Interface(accounts[s], cs.ACCOUNT) if s not in ('masked', 'absentcm'): # We can't get untyped parameters if we don't know what types # the CM gives them. assertEquals( 42, accounts[s].Properties.Get(cs.ACCOUNT, 'Parameters')['snakes']) assertEquals( dbus.UInt32, type(accounts[s].Properties.Get(cs.ACCOUNT, 'Parameters')['snakes'])) # Files in lower-priority XDG locations aren't copied until something # actually changes, and they aren't deleted. if s == 'low': assert os.path.exists(low_prio_variant_file_names[s]) # Delete the password (only), like Empathy 3.0-3.4 do when migrating. # This results in the higher-priority file being written out. account_ifaces['low'].UpdateParameters({}, ['password']) q.expect( 'dbus-signal', path=account_paths['low'], signal='AccountPropertyChanged', interface=cs.ACCOUNT, predicate=(lambda e: 'Parameters' in e.args[0]), ) # Check the account has copied (not moved! XDG_DATA_DIRS are, # conceptually, read-only) 'low' from the old to the new name assert not os.path.exists(old_key_file_name) assert not os.path.exists(newer_key_file_name) assert os.path.exists(low_prio_variant_file_names['low']) assert os.path.exists(variant_file_names['low']) # test that priority works assertContains(account_paths["priority"], properties['ValidAccounts']) assertEquals('', accounts['priority'].Properties.Get(cs.ACCOUNT, 'Nickname')) assertEquals( 'Visible', accounts['priority'].Properties.Get(cs.ACCOUNT, 'DisplayName')) # test what happens when we delete an account that has a lower-priority # "other self": it becomes masked assert accounts['priority'].Remove() is None assert not os.path.exists(old_key_file_name) assert not os.path.exists(newer_key_file_name) assert os.path.exists(low_prio_variant_file_names['priority']) assert os.path.exists(variant_file_names['priority']) assert open(variant_file_names['priority'], 'r').read() == '' assertContains('password_in_variant_file', open(low_prio_variant_file_names['priority'], 'r').read()) # The masked account is still masked assert open(variant_file_names['masked'], 'r').read() == '' # Because the CM exists, we can work out the correct types # for the 'migration' account's parameters. This triggers a commit # even though nothing has conceptually changed, so we have the type # for later. The file is copied, not moved, because XDG_DATA_DIRS are, # conceptually, read-only. assert not os.path.exists(old_key_file_name) assert not os.path.exists(newer_key_file_name) assert os.path.exists(low_prio_variant_file_names['migration']) assert os.path.exists(variant_file_names['migration']) assertEquals( "'password_in_variant_file'", account_store('get', 'variant-file', 'param-password', account=tails['migration'])) assertEquals( "uint32 42", account_store('get', 'variant-file', 'param-snakes', account=tails['migration'])) # Setting the password still does the right thing. account_ifaces['migration'].UpdateParameters({'password': '******'}, []) q.expect( 'dbus-signal', path=account_paths['migration'], signal='AccountPropertyChanged', interface=cs.ACCOUNT, predicate=(lambda e: 'Parameters' in e.args[0]), ) assert not os.path.exists(old_key_file_name) assert not os.path.exists(newer_key_file_name) assert os.path.exists(low_prio_variant_file_names['migration']) assert os.path.exists(variant_file_names['migration']) assertEquals( "'hello'", account_store('get', 'variant-file', 'param-password', account=tails['migration'])) # 'absentcm' is still only in the low-priority location: we can't # known the types of its parameters, so it doesn't get migrated. assert not os.path.exists(old_key_file_name) assert not os.path.exists(newer_key_file_name) assert os.path.exists(low_prio_variant_file_names['absentcm']) assert not os.path.exists(variant_file_names['absentcm'])
def cache_full(q, bus, conn, stream): # Test how Gabble manages the proxy cache once it's full connect_and_announce_alice(q, bus, conn, stream) send_file_to_alice(q, conn) # 3 proxies are queried (NB_MIN_SOCKS5_PROXIES) return_event, e1, e2, e3 = q.expect_many( EventPattern('dbus-return', method='CreateChannel'), EventPattern('stream-iq', iq_type='get', query_ns=ns.BYTESTREAMS), EventPattern('stream-iq', iq_type='get', query_ns=ns.BYTESTREAMS), EventPattern('stream-iq', iq_type='get', query_ns=ns.BYTESTREAMS)) send_socks5_reply(stream, e1.stanza) send_socks5_reply(stream, e2.stanza) send_socks5_reply(stream, e3.stanza) proxies = wait_si_and_return_proxies(q, stream) assertLength(3, set(proxies)) oldest_proxy = proxies[2] # send another file, one more proxy is queried send_file_to_alice(q, conn) return_event, e1, = q.expect_many( EventPattern('dbus-return', method='CreateChannel'), EventPattern('stream-iq', iq_type='get', query_ns=ns.BYTESTREAMS)) send_socks5_reply(stream, e1.stanza) proxies = wait_si_and_return_proxies(q, stream) assertLength(4, set(proxies)) # the new proxy is the head of the list assertEquals(e1.stanza['to'], proxies[0][0]) # send another file, one more proxy is queried send_file_to_alice(q, conn) return_event, e1, = q.expect_many( EventPattern('dbus-return', method='CreateChannel'), EventPattern('stream-iq', iq_type='get', query_ns=ns.BYTESTREAMS)) send_socks5_reply(stream, e1.stanza) proxies = wait_si_and_return_proxies(q, stream) assertLength(5, set(proxies)) # the new proxy is the head of the list assertEquals(e1.stanza['to'], proxies[0][0]) # send another file, one more proxy is queried send_file_to_alice(q, conn) return_event, e1, = q.expect_many( EventPattern('dbus-return', method='CreateChannel'), EventPattern('stream-iq', iq_type='get', query_ns=ns.BYTESTREAMS)) send_socks5_reply(stream, e1.stanza) proxies = wait_si_and_return_proxies(q, stream) # we reached the max size of the cache (FALLBACK_PROXY_CACHE_SIZE) so the # oldest proxy has been removed assertLength(5, set(proxies)) # the new proxy is the head of the list assertEquals(e1.stanza['to'], proxies[0][0]) # the oldest proxy has been removed assertDoesNotContain(oldest_proxy, proxies) #send another file. We already queried all the proxies so the list is recycled send_file_to_alice(q, conn) # the oldest proxy is re-requested first return_event, e1, = q.expect_many( EventPattern('dbus-return', method='CreateChannel'), EventPattern('stream-iq', to=oldest_proxy[0], iq_type='get', query_ns=ns.BYTESTREAMS))
def test(q, bus, conn, stream): room = '*****@*****.**' room_handle, chan, test_handle, bob_handle = \ join_muc_and_check(q, bus, conn, stream, room) # Exercise basic Channel Properties from spec 0.17.7 channel_props = chan.Properties.GetAll(cs.CHANNEL) assertEquals(room_handle, channel_props.get('TargetHandle')) assertEquals(cs.HT_ROOM, channel_props.get('TargetHandleType')) assertEquals(cs.CHANNEL_TYPE_TEXT, channel_props.get('ChannelType')) interfaces = channel_props.get('Interfaces') assertContains(cs.CHANNEL_IFACE_GROUP, interfaces) assertContains(cs.CHANNEL_IFACE_PASSWORD, interfaces) assertDoesNotContain(cs.TP_AWKWARD_PROPERTIES, interfaces) assertContains(cs.CHANNEL_IFACE_CHAT_STATE, interfaces) assertContains(cs.CHANNEL_IFACE_MESSAGES, interfaces) assert channel_props['TargetID'] == '*****@*****.**', channel_props assert channel_props['Requested'] == True assert channel_props['InitiatorID'] == 'test@localhost' assert channel_props['InitiatorHandle'] == conn.GetSelfHandle() # Exercise Group Properties from spec 0.17.6 (in a basic way) group_props = chan.Properties.GetAll(cs.CHANNEL_IFACE_GROUP) assert 'HandleOwners' in group_props, group_props assert 'Members' in group_props, group_props assert 'LocalPendingMembers' in group_props, group_props assert 'RemotePendingMembers' in group_props, group_props assert 'GroupFlags' in group_props, group_props # Test receiving a message from Bob in the MUC message = domish.Element((None, 'message')) message['from'] = '[email protected]/bob' message['type'] = 'groupchat' body = message.addElement('body', content='hello') stream.send(message) received, message_received = q.expect_many( EventPattern('dbus-signal', signal='Received'), EventPattern('dbus-signal', signal='MessageReceived'), ) # Check Channel.Type.Text.Received: # sender: bob assert received.args[2] == bob_handle # message type: normal assert received.args[3] == 0 # flags: none assert received.args[4] == 0 # body assert received.args[5] == 'hello' # Check Channel.Interface.Messages.MessageReceived: message = message_received.args[0] # message should have two parts: the header and one content part assert len(message) == 2, message header, body = message assert header['message-sender'] == bob_handle, header # the spec says that message-type "SHOULD be omitted for normal chat # messages." assert 'message-type' not in header, header assert body['content-type'] == 'text/plain', body assert body['content'] == 'hello', body # Remove the message from the pending message queue, and check that # PendingMessagesRemoved fires. message_id = header['pending-message-id'] chan.Text.AcknowledgePendingMessages([message_id]) removed = q.expect('dbus-signal', signal='PendingMessagesRemoved') removed_ids = removed.args[0] assert len(removed_ids) == 1, removed_ids assert removed_ids[0] == message_id, (removed_ids, message_id) # Send an action using the Messages API greeting = [ dbus.Dictionary({ 'message-type': 1, # Action }, signature='sv'), { 'content-type': 'text/plain', 'content': u"peers through a gap in the curtains", } ] # We ask for delivery reports (which MUCs provide) and read reports (which # MUCs do not provide). sent_token = chan.Messages.SendMessage(greeting, cs.MSG_SENDING_FLAGS_REPORT_DELIVERY | cs.MSG_SENDING_FLAGS_REPORT_READ) assert sent_token stream_message, sent, message_sent = q.expect_many( EventPattern('stream-message'), EventPattern('dbus-signal', signal='Sent'), EventPattern('dbus-signal', signal='MessageSent'), ) sent_message, flags, token = message_sent.args assert len(sent_message) == 2, sent_message header = sent_message[0] assert header['message-type'] == 1, header # Action assertEquals(test_handle, header['message-sender']) assertEquals('[email protected]/test', header['message-sender-id']) body = sent_message[1] assert body['content-type'] == 'text/plain', body assert body['content'] == u'peers through a gap in the curtains', body # Of the flags passed to SendMessage, Gabble should only report the # DELIVERY flag, since the other is not supported. assertEquals(cs.MSG_SENDING_FLAGS_REPORT_DELIVERY, flags) assertEquals(sent_token, token) assert sent.args[1] == 1, sent.args # Action assert sent.args[2] == u'peers through a gap in the curtains', sent.args assert message_sent.args[2] == sent_token elem = stream_message.stanza assert elem.name == 'message' assert elem['type'] == 'groupchat', repr(elem) assert elem['id'] == sent_token, repr(elem) assert elem['to'] == '*****@*****.**', repr(elem) for sub_elem in stream_message.stanza.elements(): if sub_elem.name == 'body': found_body = True assert sub_elem.children[0] == u'/me peers through a gap in the curtains' break assert found_body # reflect the sent message back to the MUC elem['from'] = '[email protected]/test' stream.send(elem) # Check that we got the corresponding delivery report report, old_received = q.expect_many( EventPattern('dbus-signal', signal='MessageReceived'), EventPattern('dbus-signal', signal='Received'), ) assert len(report.args) == 1, report.args parts = report.args[0] # The delivery report should just be a header, no body. assert len(parts) == 1, parts part = parts[0] # The intended recipient was the MUC, so there's no contact handle # suitable for being 'message-sender'. assert 'message-sender' not in part or part['message-sender'] == 0, part assert part['message-type'] == 4, part # Message_Type_Delivery_Report assert part['delivery-status'] == 1, part # Delivery_Status_Delivered assert part['delivery-token'] == sent_token, part assert 'delivery-error' not in part, part assert 'delivery-echo' in part, part # Check that the included echo is from us, and matches all the keys in the # message we sent. echo = part['delivery-echo'] assert len(echo) == len(greeting), (echo, greeting) assert echo[0]['message-sender'] == test_handle, echo[0] assert echo[0]['message-token'] == sent_token, echo[0] for i in range(0, len(echo)): for key in greeting[i]: assert key in echo[i], (i, key, echo) assert echo[i][key] == greeting[i][key], (i, key, echo, greeting) # The Text.Received signal should be a "you're not tall enough" stub id, timestamp, sender, type, flags, text = old_received.args assert sender == 0, old_received.args assert type == 4, old_received.args # Message_Type_Delivery_Report assert flags == 2, old_received.args # Non_Text_Content assert text == '', old_received.args # Send a normal message using the Channel.Type.Text API chan.Text.Send(0, 'goodbye') event, sent, message_sent = q.expect_many( EventPattern('stream-message'), EventPattern('dbus-signal', signal='Sent'), EventPattern('dbus-signal', signal='MessageSent'), ) sent_message, flags, _ = message_sent.args assert len(sent_message) == 2, sent_message header = sent_message[0] assert 'message-type' not in header, header # Normal body = sent_message[1] assert body['content-type'] == 'text/plain', body assert body['content'] == u'goodbye', body # The caller didn't ask for delivery reports (how could they? they're using # the old API), but the server's going to send us an echo anyway, so # Gabble's within its rights to pretend that the caller asked. assert flags in [0, cs.MSG_SENDING_FLAGS_REPORT_DELIVERY], flags assert sent.args[1] == 0, sent.args # Normal assert sent.args[2] == u'goodbye', sent.args sent_token = message_sent.args[2] elem = event.stanza assert elem.name == 'message' assert elem['type'] == 'groupchat' assert elem['id'] == message_sent.args[2] body = list(event.stanza.elements())[0] assert body.name == 'body' assert body.children[0] == u'goodbye' # reflect the sent message back to the MUC elem['from'] = '[email protected]/test' stream.send(elem) # TODO: check for a delivery report. # test that presence changes are sent via the MUC conn.SimplePresence.SetPresence('away', 'hurrah') event = q.expect('stream-presence', to='[email protected]/test') elem = event.stanza show = [e for e in elem.elements() if e.name == 'show'][0] assert show assert show.children[0] == u'away' status = [e for e in elem.elements() if e.name == 'status'][0] assert status assert status.children[0] == u'hurrah' # Check that there's no <x xmlns='.../muc'/> element in the <presence> # stanza when we're just updating our presence, as opposed to joining the # MUC in the first place. This is a regression test for # <https://bugs.freedesktop.org/show_bug.cgi?id=29147>. XEP-0045 §7.4 shows # that you do not need to include this element in presence updates; if we # erroneously include it, some implementations take this to mean that we're # trying to join the MUC again and helpfully send us all the scrollback # again. x_muc_nodes = xpath.queryForNodes('/presence/x[@xmlns="%s"]' % ns.MUC, elem) assert x_muc_nodes is None, elem.toXml() # test that leaving the channel results in an unavailable message chan.Group.RemoveMembers([chan.Group.GetSelfHandle()], 'booo') event = q.expect('stream-presence', to='[email protected]/test') elem = event.stanza assert elem['type'] == 'unavailable' status = [e for e in elem.elements() if e.name == 'status'] assertLength(1, status) assertEquals(status[0].children[0], u'booo')
def run_test(jp, q, bus, conn, stream, incoming): jt2 = JingleTest2(jp, conn, q, stream, "test@localhost", "[email protected]/Foo") jt2.prepare() self_handle = conn.GetSelfHandle() remote_handle = conn.RequestHandles(1, ["[email protected]/Foo"])[0] # Advertise that we can do new style calls conn.ContactCapabilities.UpdateCapabilities( [ ( cs.CLIENT + ".CallHandler", [ {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", ], ) ] ) # Ensure a channel that doesn't exist yet. if incoming: jt2.incoming_call() else: ret = conn.Requests.CreateChannel( { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CALL, cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, cs.TARGET_HANDLE: remote_handle, cs.CALL_INITIAL_AUDIO: True, } ) signal = q.expect( "dbus-signal", signal="NewChannels", predicate=lambda e: cs.CHANNEL_TYPE_CONTACT_LIST not in e.args[0][0][1].values(), ) assertLength(1, signal.args) assertLength(1, signal.args[0]) # one channel assertLength(2, signal.args[0][0]) # two struct members emitted_props = signal.args[0][0][1] assertEquals(cs.CHANNEL_TYPE_CALL, emitted_props[cs.CHANNEL_TYPE]) assertEquals(remote_handle, emitted_props[cs.TARGET_HANDLE]) assertEquals(cs.HT_CONTACT, emitted_props[cs.TARGET_HANDLE_TYPE]) assertEquals("*****@*****.**", emitted_props[cs.TARGET_ID]) assertEquals(not incoming, emitted_props[cs.REQUESTED]) if incoming: assertEquals(remote_handle, emitted_props[cs.INITIATOR_HANDLE]) assertEquals("*****@*****.**", emitted_props[cs.INITIATOR_ID]) else: assertEquals(self_handle, emitted_props[cs.INITIATOR_HANDLE]) assertEquals("test@localhost", emitted_props[cs.INITIATOR_ID]) assertEquals(True, emitted_props[cs.CALL_INITIAL_AUDIO]) assertEquals(False, emitted_props[cs.CALL_INITIAL_VIDEO]) chan = bus.get_object(conn.bus_name, signal.args[0][0][0]) properties = chan.GetAll(cs.CHANNEL_TYPE_CALL, dbus_interface=dbus.PROPERTIES_IFACE) # Check if all the properties are there assertEquals( sorted( [ "Contents", "CallMembers", "CallState", "CallFlags", "CallStateReason", "CallStateDetails", "HardwareStreaming", "InitialAudio", "InitialAudioName", "InitialVideo", "InitialVideoName", "MutableContents", ] ), sorted(properties.keys()), ) # Remote member is the target assertEquals([remote_handle], properties["CallMembers"].keys()) assertEquals(0, properties["CallMembers"][remote_handle]) # No Hardware Streaming for you assertEquals(False, properties["HardwareStreaming"]) # Only an audio content assertLength(1, properties["Contents"]) content = bus.get_object(conn.bus_name, properties["Contents"][0]) content_properties = content.GetAll(cs.CALL_CONTENT, dbus_interface=dbus.PROPERTIES_IFACE) # Has one stream assertLength(1, content_properties["Streams"]) assertEquals(cs.CALL_DISPOSITION_INITIAL, content_properties["Disposition"]) # Implements Content.Interface.Media assertEquals([cs.CALL_CONTENT_IFACE_MEDIA], content_properties["Interfaces"]) # if incoming: # assertEquals (remote_handle, content_properties["Creator"]) # else: # assertEquals (self_handle, content_properties["Creator"]) assertContains("Name", content_properties.keys()) cstream = bus.get_object(conn.bus_name, content_properties["Streams"][0]) stream_props = cstream.GetAll(cs.CALL_STREAM, dbus_interface=dbus.PROPERTIES_IFACE) assertDoesNotContain(self_handle, stream_props["RemoteMembers"].keys()) assertContains(remote_handle, stream_props["RemoteMembers"].keys()) assertEquals([cs.CALL_STREAM_IFACE_MEDIA], stream_props["Interfaces"]) if incoming: assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND, stream_props["LocalSendingState"]) assertEquals(cs.CALL_SENDING_STATE_SENDING, stream_props["RemoteMembers"][remote_handle]) else: assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND, stream_props["RemoteMembers"][remote_handle]) assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND, stream_props["LocalSendingState"]) # Media type should audio assertEquals(cs.CALL_MEDIA_TYPE_AUDIO, content_properties["Type"]) # Packetization should be RTP content_media_properties = content.GetAll(cs.CALL_CONTENT_IFACE_MEDIA, dbus_interface=dbus.PROPERTIES_IFACE) assertEquals(cs.CALL_CONTENT_PACKETIZATION_RTP, content_media_properties["Packetization"]) # Check if the channel is in the right pending state if not incoming: check_state(q, chan, cs.CALL_STATE_PENDING_INITIATOR) chan.Accept(dbus_interface=cs.CHANNEL_TYPE_CALL) check_state(q, chan, cs.CALL_STATE_PENDING_RECEIVER, wait=not incoming) # Setup codecs codecs = jt2.get_call_audio_codecs_dbus() if incoming: # Act as if we're ringing chan.SetRinging(dbus_interface=cs.CHANNEL_TYPE_CALL) signal = q.expect("dbus-signal", signal="CallStateChanged") assertEquals(cs.CALL_STATE_RINGING, signal.args[1] & cs.CALL_STATE_RINGING) # make sure this fails with NotAvailable try: content.UpdateCodecs(codecs, dbus_interface=cs.CALL_CONTENT_IFACE_MEDIA) except DBusException, e: if e.get_dbus_name() != cs.NOT_AVAILABLE: raise e
def reject_stop_receiving(self, content): content.stream.RequestReceiving(self.remote_handle, False) o = self.q.expect_many( EventPattern('dbus-signal', signal='ReceivingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_PENDING_STOP], path=content.stream.__dbus_object_path__), EventPattern('dbus-signal', signal='RemoteMembersChanged', path=content.stream.__dbus_object_path__), EventPattern('sip-invite')) assertLength(0, o[1].args[2]) assertLength(1, o[1].args[0]) assertEquals(cs.CALL_SENDING_STATE_PENDING_STOP_SENDING, o[1].args[0][self.remote_handle]) assertEquals(self.self_handle, o[1].args[3][0]) assertEquals(cs.CALL_STATE_CHANGE_REASON_USER_REQUESTED, o[1].args[3][1]) reinvite_event = o[2] self.context.check_call_sdp(reinvite_event.sip_message.body, [('audio', None, 'sendonly')]) if self.sending: body = reinvite_event.sip_message.body.replace( 'sendonly', 'sendrecv') self.context.accept(reinvite_event.sip_message, body) ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0] self.q.expect('sip-ack', cseq=ack_cseq) # Return to regular state invite_event = [EventPattern('sip-invite')] self.q.forbid_events(invite_event) content.stream.RequestReceiving(self.remote_handle, True) _, o = self.q.expect_many( EventPattern('dbus-signal', signal='ReceivingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START], path=content.stream.__dbus_object_path__), EventPattern('dbus-signal', signal='RemoteMembersChanged', path=content.stream.__dbus_object_path__, predicate=lambda e: self.remote_handle in e.args[ 0] and e.args[0][self.remote_handle] == cs. CALL_SENDING_STATE_PENDING_SEND)) assertLength(1, o.args[0]) assertLength(0, o.args[2]) assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND, o.args[0][self.remote_handle]) assertEquals(self.self_handle, o.args[3][0]) assertEquals(cs.CALL_STATE_CHANGE_REASON_USER_REQUESTED, o.args[3][1]) self.context.options_ping(self.q) self.q.unforbid_events(invite_event) content.stream.Media.CompleteReceivingStateChange( cs.CALL_STREAM_FLOW_STATE_STARTED) _, reinvite_event = self.q.expect_many( EventPattern('dbus-signal', signal='ReceivingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_STARTED], path=content.stream.__dbus_object_path__), EventPattern('sip-invite')) assertDoesNotContain('a=sendonly', reinvite_event.sip_message.body) assertDoesNotContain('a=recvonly', reinvite_event.sip_message.body) assertDoesNotContain('a=inactive', reinvite_event.sip_message.body) self.context.check_call_sdp(reinvite_event.sip_message.body) self.context.accept(reinvite_event.sip_message) ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0] o = self.q.expect_many( EventPattern('sip-ack', cseq=ack_cseq), EventPattern('dbus-signal', signal='RemoteMembersChanged', path=content.stream.__dbus_object_path__)) assertLength(0, o[1].args[2]) assertLength(1, o[1].args[0]) assertEquals(cs.CALL_SENDING_STATE_SENDING, o[1].args[0][self.remote_handle])
def reject_stop_receiving(self, content): content.stream.RequestReceiving(self.remote_handle, False) o = self.q.expect_many( EventPattern('dbus-signal', signal='ReceivingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_PENDING_STOP], path=content.stream.__dbus_object_path__), EventPattern('dbus-signal', signal='RemoteMembersChanged', path=content.stream.__dbus_object_path__), EventPattern('sip-invite')) assertLength(0, o[1].args[2]) assertLength(1, o[1].args[0]) assertEquals(cs.CALL_SENDING_STATE_PENDING_STOP_SENDING, o[1].args[0][self.remote_handle]) assertEquals(self.self_handle, o[1].args[3][0]) assertEquals(cs.CALL_STATE_CHANGE_REASON_USER_REQUESTED, o[1].args[3][1]) reinvite_event = o[2] self.context.check_call_sdp(reinvite_event.sip_message.body, [('audio', None, 'sendonly')]) if self.sending: body = reinvite_event.sip_message.body.replace('sendonly', 'sendrecv') self.context.accept(reinvite_event.sip_message, body) ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0] self.q.expect('sip-ack', cseq=ack_cseq) # Return to regular state invite_event = [EventPattern('sip-invite')] self.q.forbid_events(invite_event) content.stream.RequestReceiving(self.remote_handle, True) _ , o = self.q.expect_many( EventPattern('dbus-signal', signal='ReceivingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START], path=content.stream.__dbus_object_path__), EventPattern('dbus-signal', signal='RemoteMembersChanged', path=content.stream.__dbus_object_path__, predicate=lambda e: self.remote_handle in e.args[0] and e.args[0][self.remote_handle] == cs.CALL_SENDING_STATE_PENDING_SEND)) assertLength(1, o.args[0]) assertLength(0, o.args[2]) assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND, o.args[0][self.remote_handle]) assertEquals(self.self_handle, o.args[3][0]) assertEquals(cs.CALL_STATE_CHANGE_REASON_USER_REQUESTED, o.args[3][1]) self.context.options_ping(self.q) self.q.unforbid_events(invite_event) content.stream.Media.CompleteReceivingStateChange( cs.CALL_STREAM_FLOW_STATE_STARTED) _, reinvite_event = self.q.expect_many( EventPattern('dbus-signal', signal='ReceivingStateChanged', args=[cs.CALL_STREAM_FLOW_STATE_STARTED], path=content.stream.__dbus_object_path__), EventPattern('sip-invite')) assertDoesNotContain('a=sendonly', reinvite_event.sip_message.body) assertDoesNotContain('a=recvonly', reinvite_event.sip_message.body) assertDoesNotContain('a=inactive', reinvite_event.sip_message.body) self.context.check_call_sdp(reinvite_event.sip_message.body) self.context.accept(reinvite_event.sip_message) ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0] o = self.q.expect_many( EventPattern('sip-ack', cseq=ack_cseq), EventPattern('dbus-signal', signal='RemoteMembersChanged', path=content.stream.__dbus_object_path__)) assertLength(0, o[1].args[2]) assertLength(1, o[1].args[0]) assertEquals(cs.CALL_SENDING_STATE_SENDING, o[1].args[0][self.remote_handle])
def test_ft_caps_from_contact(q, bus, conn, 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 FT capa ver = compute_caps_hash([], [ns.IQ_OOB], {}) txt_record = { "txtvers": "1", "status": "avail", "node": client, "ver": ver, "hash": "sha-1" } contact_name = "test-caps-ft@" + get_host_name() listener, port = setup_stream_listener(q, contact_name) announcer = AvahiAnnouncer(contact_name, "_presence._tcp", port, txt_record) # this is the first presence, Salut connects to the contact e = q.expect('incoming-connection', listener=listener) incoming = e.connection # Salut looks up our capabilities event = q.expect('stream-iq', connection=incoming, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + ver, (query_node.attributes['node'], client, ver) contact_handle = conn_contacts_iface.GetContactByID(contact_name, [])[0] # send good reply result = make_result_iq(event.stanza) query = result.firstChildElement() query['node'] = client + '#' + ver feature = query.addElement('feature') feature['var'] = ns.IQ_OOB incoming.send(result) # FT capa is announced e = q.expect('dbus-signal', signal='ContactCapabilitiesChanged', predicate=lambda e: contact_handle in e.args[0]) caps = e.args[0][contact_handle] assertContains(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.CONN_IFACE_CONTACT_CAPS + '/capabilities'] assert caps_via_contacts_iface == caps, caps_via_contacts_iface # check if Salut announces the OOB capa self_handle = conn.Properties.Get(cs.CONN, "SelfHandle") self_handle_name = conn.Properties.Get(cs.CONN, "SelfID") AvahiListener(q).listen_for_service("_presence._tcp") e = q.expect('service-added', name=self_handle_name, protocol=avahi.PROTO_INET) service = e.service service.resolve() receive_presence_and_ask_caps(q, incoming, service, contact_name) # capa announced without FT ver = compute_caps_hash([], ["http://telepathy.freedesktop.org/xmpp/pony"], {}) txt_record = { "txtvers": "1", "status": "avail", "node": client, "ver": ver, "hash": "sha-1" } contact_name = "test-caps-ft2@" + get_host_name() listener, port = setup_stream_listener(q, contact_name) announcer = AvahiAnnouncer(contact_name, "_presence._tcp", port, txt_record) # this is the first presence, Salut connects to the contact e = q.expect('incoming-connection', listener=listener) incoming = e.connection # Salut looks up our capabilities event = q.expect('stream-iq', connection=incoming, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + ver, (query_node.attributes['node'], client, ver) contact_handle = conn_contacts_iface.GetContactByID(contact_name, [])[0] # send good reply result = make_result_iq(event.stanza) query = result.firstChildElement() query['node'] = client + '#' + ver feature = query.addElement('feature') feature['var'] = "http://telepathy.freedesktop.org/xmpp/pony" incoming.send(result) # the FT capability is not announced e = q.expect('dbus-signal', signal='ContactCapabilitiesChanged') caps = e.args[0][contact_handle] assertDoesNotContain(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.CONN_IFACE_CONTACT_CAPS + '/capabilities'] assert caps_via_contacts_iface == caps, caps_via_contacts_iface # no capabilites announced (assume FT is supported to insure interop) txt_record = {"txtvers": "1", "status": "avail"} contact_name = "test-caps-ft-no-capa2@" + get_host_name() contact_handle = conn_contacts_iface.GetContactByID(contact_name, [])[0] listener, port = setup_stream_listener(q, contact_name) announcer = AvahiAnnouncer(contact_name, "_presence._tcp", port, txt_record) # FT capa is announced e = q.expect('dbus-signal', signal='ContactCapabilitiesChanged', predicate=lambda e: contact_handle in e.args[0].keys()) caps = e.args[0][contact_handle] assertContains(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.CONN_IFACE_CONTACT_CAPS + '/capabilities'] assert caps_via_contacts_iface == caps, caps_via_contacts_iface
def test(q, bus, conn, stream): # Ignore conn here, we're only dealing with the CM and Protocol objects. cm = bus.get_object(cs.CM + '.haze', tp_path_prefix + '/ConnectionManager/haze') cm_iface = dbus.Interface(cm, cs.CM) cm_props = dbus.Interface(cm, cs.PROPERTIES_IFACE) protocols = cm_props.Get(cs.CM, 'Protocols') protocol_names = cm_iface.ListProtocols() assertEquals(set(protocols.iterkeys()), set(protocol_names)) for name in protocol_names: props = protocols[name] protocol = bus.get_object(cm.bus_name, cm.object_path + '/' + name.replace('-', '_')) protocol_iface = dbus.Interface(protocol, cs.PROTOCOL) protocol_props = dbus.Interface(protocol, cs.PROPERTIES_IFACE) flat_props = protocol_props.GetAll(cs.PROTOCOL) protocol_avatar_props = protocol_props.GetAll(cs.PROTOCOL_IFACE_AVATARS) # Protocol is supposed to implement Interface.Avatars iff the # connection implements Avatars as well. if cs.CONN_IFACE_AVATARS in flat_props['ConnectionInterfaces']: assertContains(cs.PROTOCOL_IFACE_AVATARS, props[cs.PROTOCOL + '.Interfaces']) else: assertDoesNotContain(cs.PROTOCOL_IFACE_AVATARS, props[cs.PROTOCOL + '.Interfaces']) parameters = cm_iface.GetParameters(name) assertEquals(parameters, props[cs.PROTOCOL + '.Parameters']) assertEquals(parameters, flat_props['Parameters']) assertEquals(parameters, protocol_props.Get(cs.PROTOCOL, 'Parameters')) assertEquals(flat_props['VCardField'], props[cs.PROTOCOL + '.VCardField']) assertEquals(flat_props['Interfaces'], props[cs.PROTOCOL + '.Interfaces']) assertEquals(flat_props['EnglishName'], props[cs.PROTOCOL + '.EnglishName']) assertEquals(flat_props['Icon'], props[cs.PROTOCOL + '.Icon']) assertEquals(flat_props['ConnectionInterfaces'], props[cs.PROTOCOL + '.ConnectionInterfaces']) assertEquals(flat_props['RequestableChannelClasses'], props[cs.PROTOCOL + '.RequestableChannelClasses']) param_map = {} param_flags = {} param_type = {} param_def = {} for p in parameters: param_map[p[0]] = tuple(p[1:]) param_flags[p[0]] = p[1] param_type[p[0]] = p[2] param_def[p[0]] = p[3] # We use special cases to rename these; make sure they don't come back assertDoesNotContain(name, ('meanwhile', 'simple')) assertDoesNotContain('encoding', param_map) assertDoesNotContain('local_charset', param_map) if name not in ('local-xmpp', 'irc'): # it would be more correct for these protocols not to have this # parameter assertEquals((cs.PARAM_REQUIRED, 's', ''), param_map['account']) # a random selection of checks for known parameters... if name == 'gadugadu': assertEquals('x-gadugadu', flat_props['VCardField']) assertEquals('im-gadugadu', flat_props['Icon']) assertEquals((cs.PARAM_SECRET, 's', ''), param_map['password']) assertEquals('s', param_type['nick']) assertEquals('s', param_type['gg-server']) elif name == 'silc': assertEquals('x-silc', flat_props['VCardField']) assertEquals('im-silc', flat_props['Icon']) assertEquals((cs.PARAM_SECRET, 's', ''), param_map['password']) assertEquals('s', param_type['server']) elif name == 'irc': assertEquals('x-irc', flat_props['VCardField']) assertEquals('im-irc', flat_props['Icon']) assertEquals((cs.PARAM_SECRET, 's', ''), param_map['password']) assertEquals('s', param_type['charset']) assertEquals('s', param_type['username']) assertEquals('s', param_type['realname']) assertEquals('s', param_type['server']) assertEquals(cs.PARAM_HAS_DEFAULT, param_flags['server']) assertEquals('*****@*****.**', protocol_iface.IdentifyAccount({ 'account': 'smcv', 'server': 'irc.debian.org'})) assertDoesNotContain(cs.CONN_IFACE_AVATARS, flat_props['ConnectionInterfaces']) assertDoesNotContain(cs.CONN_IFACE_CONTACT_BLOCKING, flat_props['ConnectionInterfaces']) assertDoesNotContain(cs.CONN_IFACE_MAIL_NOTIFICATION, flat_props['ConnectionInterfaces']) # Avatar not supported assertEquals(0, protocol_avatar_props['MaximumAvatarBytes']) assertEquals(0, protocol_avatar_props['MaximumAvatarHeight']) assertEquals(0, protocol_avatar_props['MaximumAvatarWidth']) assertEquals(0, protocol_avatar_props['MinimumAvatarHeight']) assertEquals(0, protocol_avatar_props['MinimumAvatarWidth']) assertEquals(0, protocol_avatar_props['RecommendedAvatarHeight']) assertEquals(0, protocol_avatar_props['RecommendedAvatarWidth']) assertEquals([], protocol_avatar_props['SupportedAvatarMIMETypes']) elif name == 'myspace': assertEquals('x-myspace', flat_props['VCardField']) assertEquals('im-myspace', flat_props['Icon']) assertEquals('s', param_type['server']) elif name == 'yahoo': assertEquals('x-yahoo', flat_props['VCardField']) assertEquals('im-yahoo', flat_props['Icon']) assertEquals('s', param_type['charset']) elif name == 'yahoojp': assertEquals('x-yahoo', flat_props['VCardField']) assertEquals('im-yahoojp', flat_props['Icon']) assertEquals('s', param_type['charset']) elif name == 'aim': assertEquals('x-aim', flat_props['VCardField']) assertEquals('im-aim', flat_props['Icon']) assertEquals('s', param_type['server']) elif name == 'msn': assertEquals('x-msn', flat_props['VCardField']) assertEquals('im-msn', flat_props['Icon']) assertEquals('s', param_type['server']) elif name == 'jabber': assertEquals('x-jabber', flat_props['VCardField']) assertEquals('im-jabber', flat_props['Icon']) assertDoesNotContain('require_tls', param_map) assertDoesNotContain('connect_server', param_map) assertEquals((cs.PARAM_SECRET, 's', ''), param_map['password']) assertEquals((cs.PARAM_HAS_DEFAULT, 'b', True), param_map['require-encryption']) assertEquals('*****@*****.**', protocol_iface.IdentifyAccount({ 'account': '*****@*****.**', 'password': '******'})) assertEquals(r'*****@*****.**', protocol_iface.IdentifyAccount({ 'account': '*****@*****.**', 'server': r'corp.example.com', 'password': '******'})) # this contains an unsupported parameter call_async(q, protocol_iface, 'IdentifyAccount', { 'account': '*****@*****.**', 'embrace-and-extend': r'WORKGROUP\Bill', 'password': '******'}) q.expect('dbus-error', name=cs.INVALID_ARGUMENT) assertContains(cs.CONN_IFACE_AVATARS, flat_props['ConnectionInterfaces']) assertContains(cs.CONN_IFACE_CONTACT_BLOCKING, flat_props['ConnectionInterfaces']) assertContains(cs.CONN_IFACE_MAIL_NOTIFICATION, flat_props['ConnectionInterfaces']) # libpurple currently says there's no max size assertEquals(0, protocol_avatar_props['MaximumAvatarBytes']) assertEquals(96, protocol_avatar_props['MaximumAvatarHeight']) assertEquals(96, protocol_avatar_props['MaximumAvatarWidth']) assertEquals(32, protocol_avatar_props['MinimumAvatarHeight']) assertEquals(32, protocol_avatar_props['MinimumAvatarWidth']) assertEquals(0, protocol_avatar_props['RecommendedAvatarHeight']) assertEquals(0, protocol_avatar_props['RecommendedAvatarWidth']) assertEquals(['image/png'], protocol_avatar_props['SupportedAvatarMIMETypes']) elif name == 'qq': assertEquals('x-qq', flat_props['VCardField']) assertEquals('im-qq', flat_props['Icon']) elif name == 'sametime': assertEquals('x-sametime', flat_props['VCardField']) assertEquals('im-sametime', flat_props['Icon']) elif name == 'zephyr': assertEquals('x-zephyr', flat_props['VCardField']) assertEquals('im-zephyr', flat_props['Icon']) assertEquals('s', param_type['realm']) assertEquals('s', param_type['charset']) elif name == 'local-xmpp': # makes very little sense in an address book assertEquals('', flat_props['VCardField']) assertEquals('im-local-xmpp', flat_props['Icon']) assertEquals((cs.PARAM_REQUIRED, 's', ''), param_map['first-name']) assertDoesNotContain('first', param_map) assertEquals((cs.PARAM_REQUIRED, 's', ''), param_map['last-name']) assertDoesNotContain('last', param_map) assertEquals((0, 's', ''), param_map['email']) assertEquals((0, 's', ''), param_map['jid']) elif name == 'icq': assertEquals('x-icq', flat_props['VCardField']) assertEquals('im-icq', flat_props['Icon']) elif name == 'groupwise': assertEquals('x-groupwise', flat_props['VCardField']) assertEquals('im-groupwise', flat_props['Icon']) elif name == 'sipe': assertEquals('im-sipe', flat_props['Icon']) assertDoesNotContain('usersplit1', param_map) assertEquals((cs.PARAM_HAS_DEFAULT, 's', ''), param_map['login']) assertEquals('[email protected],', protocol_iface.IdentifyAccount({ 'account': '*****@*****.**', 'password': '******'})) assertEquals(r'[email protected],WORKGROUP\Bill', protocol_iface.IdentifyAccount({ 'account': '*****@*****.**', 'login': r'WORKGROUP\Bill', 'password': '******'}))
def test_ft_caps_from_contact(q, bus, conn, 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 FT capa ver = compute_caps_hash([], [ns.IQ_OOB], {}) txt_record = { "txtvers": "1", "status": "avail", "node": client, "ver": ver, "hash": "sha-1"} contact_name = "test-caps-ft@" + get_host_name() listener, port = setup_stream_listener(q, contact_name) announcer = AvahiAnnouncer(contact_name, "_presence._tcp", port, txt_record) # this is the first presence, Salut connects to the contact e = q.expect('incoming-connection', listener = listener) incoming = e.connection # Salut looks up our capabilities event = q.expect('stream-iq', connection = incoming, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + ver, (query_node.attributes['node'], client, ver) contact_handle = conn_contacts_iface.GetContactByID(contact_name, [])[0] # send good reply result = make_result_iq(event.stanza) query = result.firstChildElement() query['node'] = client + '#' + ver feature = query.addElement('feature') feature['var'] = ns.IQ_OOB incoming.send(result) # FT capa is announced e = q.expect('dbus-signal', signal='ContactCapabilitiesChanged', predicate=lambda e: contact_handle in e.args[0]) caps = e.args[0][contact_handle] assertContains(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.CONN_IFACE_CONTACT_CAPS + '/capabilities'] assert caps_via_contacts_iface == caps, caps_via_contacts_iface # check if Salut announces the OOB capa self_handle = conn.Properties.Get(cs.CONN, "SelfHandle") self_handle_name = conn.Properties.Get(cs.CONN, "SelfID") AvahiListener(q).listen_for_service("_presence._tcp") e = q.expect('service-added', name = self_handle_name, protocol = avahi.PROTO_INET) service = e.service service.resolve() receive_presence_and_ask_caps(q, incoming, service, contact_name) # capa announced without FT ver = compute_caps_hash([], ["http://telepathy.freedesktop.org/xmpp/pony"], {}) txt_record = { "txtvers": "1", "status": "avail", "node": client, "ver": ver, "hash": "sha-1"} contact_name = "test-caps-ft2@" + get_host_name() listener, port = setup_stream_listener(q, contact_name) announcer = AvahiAnnouncer(contact_name, "_presence._tcp", port, txt_record) # this is the first presence, Salut connects to the contact e = q.expect('incoming-connection', listener = listener) incoming = e.connection # Salut looks up our capabilities event = q.expect('stream-iq', connection = incoming, query_ns='http://jabber.org/protocol/disco#info') query_node = xpath.queryForNodes('/iq/query', event.stanza)[0] assert query_node.attributes['node'] == \ client + '#' + ver, (query_node.attributes['node'], client, ver) contact_handle = conn_contacts_iface.GetContactByID(contact_name, [])[0] # send good reply result = make_result_iq(event.stanza) query = result.firstChildElement() query['node'] = client + '#' + ver feature = query.addElement('feature') feature['var'] = "http://telepathy.freedesktop.org/xmpp/pony" incoming.send(result) # the FT capability is not announced e = q.expect('dbus-signal', signal='ContactCapabilitiesChanged') caps = e.args[0][contact_handle] assertDoesNotContain(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.CONN_IFACE_CONTACT_CAPS + '/capabilities'] assert caps_via_contacts_iface == caps, caps_via_contacts_iface # no capabilites announced (assume FT is supported to insure interop) txt_record = { "txtvers": "1", "status": "avail"} contact_name = "test-caps-ft-no-capa2@" + get_host_name() contact_handle = conn_contacts_iface.GetContactByID(contact_name, [])[0] listener, port = setup_stream_listener(q, contact_name) announcer = AvahiAnnouncer(contact_name, "_presence._tcp", port, txt_record) # FT capa is announced e = q.expect('dbus-signal', signal='ContactCapabilitiesChanged', predicate=lambda e: contact_handle in e.args[0].keys()) caps = e.args[0][contact_handle] assertContains(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.CONN_IFACE_CONTACT_CAPS + '/capabilities'] assert caps_via_contacts_iface == caps, caps_via_contacts_iface
def store_content(self, content_path, initial = True, incoming = None): if incoming is None: incoming = self.incoming content = wrap_content(self.bus.get_object(self.conn.bus_name, content_path), ['DTMF', 'Media']) content_props = content.GetAll(cs.CALL_CONTENT, dbus_interface=dbus.PROPERTIES_IFACE) # Has one stream assertLength(1, content_props["Streams"]) if initial: assertEquals(cs.CALL_DISPOSITION_INITIAL, content_props["Disposition"]) else: assertEquals(cs.CALL_DISPOSITION_NONE, content_props["Disposition"]) # Implements Content.Interface.Media assertContains(cs.CALL_CONTENT_IFACE_MEDIA, content_props["Interfaces"]) if content_props['Type'] == cs.CALL_MEDIA_TYPE_AUDIO: # Implements Content.Interface.DTMF assertContains(cs.CALL_CONTENT_IFACE_DTMF, content_props["Interfaces"]) assertContains("Name", content_props.keys()) content_name = content_props["Name"] stream = self.bus.get_object(self.conn.bus_name, content_props["Streams"][0]) stream_props = stream.GetAll(cs.CALL_STREAM, dbus_interface = dbus.PROPERTIES_IFACE) assertDoesNotContain(self.self_handle, stream_props["RemoteMembers"].keys()) assertContains(self.peer_handle, stream_props["RemoteMembers"].keys()) assertEquals([cs.CALL_STREAM_IFACE_MEDIA], stream_props["Interfaces"]) assertEquals(self.can_change_direction, stream_props["CanRequestReceiving"]) if incoming: assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND, stream_props["LocalSendingState"]) assertEquals(cs.CALL_SENDING_STATE_SENDING, stream_props["RemoteMembers"][self.peer_handle]) else: if initial: assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND, stream_props["RemoteMembers"][self.peer_handle]) else: assertEquals(cs.CALL_SENDING_STATE_SENDING, stream_props["RemoteMembers"][self.peer_handle]) assertEquals(cs.CALL_SENDING_STATE_SENDING, stream_props["LocalSendingState"]) # Packetization should be RTP content_media_props = content.GetAll(cs.CALL_CONTENT_IFACE_MEDIA, dbus_interface=dbus.PROPERTIES_IFACE) assertEquals(cs.CALL_CONTENT_PACKETIZATION_RTP, content_media_props["Packetization"]) # Check the directions stream_media_props = stream.GetAll(cs.CALL_STREAM_IFACE_MEDIA, dbus_interface=dbus.PROPERTIES_IFACE) if initial or incoming: assertEquals(cs.CALL_STREAM_FLOW_STATE_STOPPED, stream_media_props["SendingState"]) else: assertEquals(cs.CALL_STREAM_FLOW_STATE_PENDING_START, stream_media_props["SendingState"]) if initial: assertEquals(cs.CALL_STREAM_FLOW_STATE_STOPPED, stream_media_props["ReceivingState"]) else: assertEquals(cs.CALL_STREAM_FLOW_STATE_PENDING_START, stream_media_props["ReceivingState"]) assertEquals(False, stream_media_props["ICERestartPending"]) # Store the content and stream if content_props['Type'] == cs.CALL_MEDIA_TYPE_AUDIO: assert self.initial_audio == initial assert self.audio_content == None assert self.audio_stream == None self.audio_content = content self.audio_content_name = content_name self.audio_stream = stream elif content_props['Type'] == cs.CALL_MEDIA_TYPE_VIDEO: assert self.initial_video == initial assert self.video_content == None assert self.video_stream == None self.video_content = content self.video_content_name = content_name self.video_stream = stream else: assert not 'Bad content type value'
def test(q, bus, conn, stream): event = q.expect('stream-iq', query_ns=ns.ROSTER) event.stanza['type'] = 'result' item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'both' item.addElement('group', content='women') item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'from' item.addElement('group', content='men') item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'to' item.addElement('group', content='men') stream.send(event.stanza) # slight implementation detail: TpBaseContactList emits ContactsChanged # etc. before it announces its channels, and it emits one CGC per group. s1, s2 = q.expect_many( EventPattern('dbus-signal', signal='GroupsChanged', interface=cs.CONN_IFACE_CONTACT_GROUPS, path=conn.object_path, predicate=lambda e: 'women' in e.args[1]), EventPattern('dbus-signal', signal='GroupsChanged', interface=cs.CONN_IFACE_CONTACT_GROUPS, path=conn.object_path, predicate=lambda e: 'men' in e.args[1]), ) amy, bob, che = conn.RequestHandles(cs.HT_CONTACT, ['*****@*****.**', '*****@*****.**', '*****@*****.**']) assertEquals([[amy], ['women'], []], s1.args) assertEquals([[bob, che], ['men'], []], s2.args) pairs = expect_contact_list_signals(q, bus, conn, [], ['men', 'women']) q.expect('dbus-signal', signal='ContactListStateChanged', args=[cs.CONTACT_LIST_STATE_SUCCESS]) check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_GROUP, 'men', ['*****@*****.**', '*****@*****.**']) check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_GROUP, 'women', ['*****@*****.**']) assertLength(0, pairs) # i.e. we've checked all of them # change Amy's groups call_async(q, conn.ContactGroups, 'SetContactGroups', amy, ['ladies', 'people starting with A']) s, iq = q.expect_many( EventPattern('dbus-signal', signal='GroupsCreated'), EventPattern('stream-iq', iq_type='set', query_name='query', query_ns=ns.ROSTER), ) assertEquals(set(('ladies', 'people starting with A')), set(s.args[0])) jid, groups = parse_roster_change_request(iq.query, iq.stanza) assertEquals('*****@*****.**', jid) assertEquals(set(('ladies', 'people starting with A')), groups) acknowledge_iq(stream, iq.stanza) q.expect('dbus-return', method='SetContactGroups') # Now the server sends us a roster push. send_roster_push(stream, '*****@*****.**', ['people starting with A', 'ladies']) # We get a single signal corresponding to that roster push e = q.expect('dbus-signal', signal='GroupsChanged', predicate=lambda e: e.args[0] == [amy]) assertEquals(set(['ladies', 'people starting with A']), set(e.args[1])) assertEquals(['women'], e.args[2]) # check that Amy's state is what we expected attrs = conn.Contacts.GetContactAttributes([amy], [cs.CONN_IFACE_CONTACT_GROUPS], False)[amy] # make the group list order-independent attrs[cs.CONN_IFACE_CONTACT_GROUPS + '/groups'] = \ set(attrs[cs.CONN_IFACE_CONTACT_GROUPS + '/groups']) assertEquals({ cs.CONN_IFACE_CONTACT_GROUPS + '/groups': set(['ladies', 'people starting with A']), cs.CONN + '/contact-id': '*****@*****.**' }, attrs) for it_worked in (False, True): # remove a group with a member (the old API couldn't do this) call_async(q, conn.ContactGroups, 'RemoveGroup', 'people starting with A') iq = q.expect('stream-iq', iq_type='set', query_name='query', query_ns=ns.ROSTER) jid, groups = parse_roster_change_request(iq.query, iq.stanza) assertEquals('*****@*****.**', jid) assertEquals(set(('ladies',)), groups) acknowledge_iq(stream, iq.stanza) # we emit these as soon as the IQ is ack'd, so that we can indicate # group removal... q.expect('dbus-signal', signal='GroupsRemoved', args=[['people starting with A']]) q.expect('dbus-signal', signal='GroupsChanged', args=[[amy], [], ['people starting with A']]) q.expect('dbus-return', method='RemoveGroup') if it_worked: # ... although in fact this is what *actually* removes Amy from the # group send_roster_push(stream, '*****@*****.**', ['ladies']) else: # if the change didn't "stick", this message will revert it send_roster_push(stream, '*****@*****.**', ['ladies', 'people starting with A']) q.expect('dbus-signal', signal='GroupsCreated', args=[['people starting with A']]) q.expect('dbus-signal', signal='GroupsChanged', args=[[amy], ['people starting with A'], []]) sync_dbus(bus, q, conn) sync_stream(q, stream) assertEquals({ cs.CONN_IFACE_CONTACT_GROUPS + '/groups': ['ladies', 'people starting with A'], cs.CONN + '/contact-id': '*****@*****.**' }, conn.Contacts.GetContactAttributes([amy], [cs.CONN_IFACE_CONTACT_GROUPS], False)[amy]) # sanity check: after all that, we expect Amy to be in group 'ladies' only sync_dbus(bus, q, conn) sync_stream(q, stream) assertEquals({ cs.CONN_IFACE_CONTACT_GROUPS + '/groups': ['ladies'], cs.CONN + '/contact-id': '*****@*****.**' }, conn.Contacts.GetContactAttributes([amy], [cs.CONN_IFACE_CONTACT_GROUPS], False)[amy]) # Rename group 'ladies' to 'girls' call_async(q, conn.ContactGroups, 'RenameGroup', 'ladies', 'girls') # Amy is added to 'girls' e = q.expect('stream-iq', iq_type='set', query_name='query', query_ns=ns.ROSTER) jid, groups = parse_roster_change_request(e.query, e.stanza) assertEquals('*****@*****.**', jid) assertEquals(set(['girls', 'ladies']), groups) send_roster_push(stream, '*****@*****.**', ['girls', 'ladies']) acknowledge_iq(stream, e.stanza) # Amy is removed from 'ladies' e = q.expect('stream-iq', iq_type='set', query_name='query', query_ns=ns.ROSTER) jid, groups = parse_roster_change_request(e.query, e.stanza) assertEquals('*****@*****.**', jid) assertEquals(set(['girls']), groups) send_roster_push(stream, '*****@*****.**', ['girls']) acknowledge_iq(stream, e.stanza) q.expect('dbus-return', method='RenameGroup') # check everything has been updated groups = conn.Properties.Get(cs.CONN_IFACE_CONTACT_GROUPS, 'Groups') assertContains('girls', groups) assertDoesNotContain('ladies', groups) contacts = conn.ContactList.GetContactListAttributes([cs.CONN_IFACE_CONTACT_GROUPS], False) assertEquals(['girls'], contacts[amy][cs.CONN_IFACE_CONTACT_GROUPS + '/groups'])
def store_content(self, content_path, initial = True, incoming = None): if incoming is None: incoming = self.incoming content = wrap_content(self.bus.get_object(self.conn.bus_name, content_path)) content_props = content.GetAll(cs.CALL_CONTENT, dbus_interface=dbus.PROPERTIES_IFACE) # Has one stream assertLength(1, content_props["Streams"]) if initial: assertEquals(cs.CALL_DISPOSITION_INITIAL, content_props["Disposition"]) else: assertEquals(cs.CALL_DISPOSITION_NONE, content_props["Disposition"]) # Implements Content.Interface.Media assertContains(cs.CALL_CONTENT_IFACE_MEDIA, content_props["Interfaces"]) if content_props['Type'] == cs.CALL_MEDIA_TYPE_AUDIO: # Implements Content.Interface.DTMF assertContains(cs.CALL_CONTENT_IFACE_DTMF, content_props["Interfaces"]) assertContains("Name", content_props.keys()) content_name = content_props["Name"] stream = self.bus.get_object(self.conn.bus_name, content_props["Streams"][0]) stream_props = stream.GetAll(cs.CALL_STREAM, dbus_interface = dbus.PROPERTIES_IFACE) assertDoesNotContain(self.self_handle, stream_props["RemoteMembers"].keys()) assertContains(self.peer_handle, stream_props["RemoteMembers"].keys()) assertEquals([cs.CALL_STREAM_IFACE_MEDIA], stream_props["Interfaces"]) assertEquals(self.can_change_direction, stream_props["CanRequestReceiving"]) if incoming: assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND, stream_props["LocalSendingState"]) assertEquals(cs.CALL_SENDING_STATE_SENDING, stream_props["RemoteMembers"][self.peer_handle]) else: if initial: assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND, stream_props["RemoteMembers"][self.peer_handle]) else: assertEquals(cs.CALL_SENDING_STATE_SENDING, stream_props["RemoteMembers"][self.peer_handle]) assertEquals(cs.CALL_SENDING_STATE_SENDING, stream_props["LocalSendingState"]) # Packetization should be RTP content_media_props = content.GetAll(cs.CALL_CONTENT_IFACE_MEDIA, dbus_interface=dbus.PROPERTIES_IFACE) assertEquals(cs.CALL_CONTENT_PACKETIZATION_RTP, content_media_props["Packetization"]) # Check the directions stream_media_props = stream.GetAll(cs.CALL_STREAM_IFACE_MEDIA, dbus_interface=dbus.PROPERTIES_IFACE) if initial or incoming: assertEquals(cs.CALL_STREAM_FLOW_STATE_STOPPED, stream_media_props["SendingState"]) else: assertEquals(cs.CALL_STREAM_FLOW_STATE_PENDING_START, stream_media_props["SendingState"]) if initial: assertEquals(cs.CALL_STREAM_FLOW_STATE_STOPPED, stream_media_props["ReceivingState"]) else: assertEquals(cs.CALL_STREAM_FLOW_STATE_PENDING_START, stream_media_props["ReceivingState"]) assertEquals(False, stream_media_props["ICERestartPending"]) # Store the content and stream if content_props['Type'] == cs.CALL_MEDIA_TYPE_AUDIO: assert self.initial_audio == initial assert self.audio_content == None assert self.audio_stream == None self.audio_content = content self.audio_content_name = content_name self.audio_stream = stream elif content_props['Type'] == cs.CALL_MEDIA_TYPE_VIDEO: assert self.initial_video == initial assert self.video_content == None assert self.video_stream == None self.video_content = content self.video_content_name = content_name self.video_stream = stream else: assert not 'Bad content type value'
def test(q, bus, conn, stream): event = q.expect('stream-iq', query_ns=ns.ROSTER) event.stanza['type'] = 'result' item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'both' item.addElement('group', content='women') item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'from' item.addElement('group', content='men') item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'to' item.addElement('group', content='men') stream.send(event.stanza) # Avoid relying on the implementation detail of exactly when # TpBaseContactList emits ContactsChanged, relative to when it # announces its channels. Prior to 0.20.3, 0.21.1 it would # announce the channels, emit GroupsChanged, then announce the channels # again... which was a bug, but it turned out this test relied on it. # # We do still rely on the implementation detail that we emit GroupsChanged # once per group with all of its members, not once per contact with all # of their groups. On a typical contact list, there are more contacts # than groups, so that'll work out smaller. q.expect_many( EventPattern('dbus-signal', signal='GroupsCreated', interface=cs.CONN_IFACE_CONTACT_GROUPS, path=conn.object_path, predicate=lambda e: groups_created_predicate(e, ['men', 'women'])), EventPattern('dbus-signal', signal='GroupsChanged', interface=cs.CONN_IFACE_CONTACT_GROUPS, path=conn.object_path, predicate=lambda e: groups_changed_predicate(e, conn, ['*****@*****.**'], ['women'], [])), EventPattern('dbus-signal', signal='GroupsChanged', interface=cs.CONN_IFACE_CONTACT_GROUPS, path=conn.object_path, predicate=lambda e: groups_changed_predicate(e, conn, ['*****@*****.**', '*****@*****.**'], ['men'], [])), ) amy, bob, che = conn.get_contact_handles_sync( ['*****@*****.**', '*****@*****.**', '*****@*****.**']) q.expect('dbus-signal', signal='ContactListStateChanged', args=[cs.CONTACT_LIST_STATE_SUCCESS]) # change Amy's groups call_async(q, conn.ContactGroups, 'SetContactGroups', amy, ['ladies', 'people starting with A']) s, iq = q.expect_many( EventPattern('dbus-signal', signal='GroupsCreated'), EventPattern('stream-iq', iq_type='set', query_name='query', query_ns=ns.ROSTER), ) assertEquals(set(('ladies', 'people starting with A')), set(s.args[0])) jid, groups = parse_roster_change_request(iq.query, iq.stanza) assertEquals('*****@*****.**', jid) assertEquals(set(('ladies', 'people starting with A')), groups) acknowledge_iq(stream, iq.stanza) q.expect('dbus-return', method='SetContactGroups') # Now the server sends us a roster push. send_roster_push(stream, '*****@*****.**', ['people starting with A', 'ladies']) # We get a single signal corresponding to that roster push e = q.expect('dbus-signal', signal='GroupsChanged', predicate=lambda e: e.args[0] == [amy]) assertEquals(set(['ladies', 'people starting with A']), set(e.args[1])) assertEquals(['women'], e.args[2]) # check that Amy's state is what we expected attrs = conn.Contacts.GetContactAttributes([amy], [cs.CONN_IFACE_CONTACT_GROUPS], False)[amy] # make the group list order-independent attrs[cs.CONN_IFACE_CONTACT_GROUPS + '/groups'] = \ set(attrs[cs.CONN_IFACE_CONTACT_GROUPS + '/groups']) assertEquals({ cs.CONN_IFACE_CONTACT_GROUPS + '/groups': set(['ladies', 'people starting with A']), cs.CONN + '/contact-id': '*****@*****.**' }, attrs) for it_worked in (False, True): # remove a group with a member (the old API couldn't do this) call_async(q, conn.ContactGroups, 'RemoveGroup', 'people starting with A') iq = q.expect('stream-iq', iq_type='set', query_name='query', query_ns=ns.ROSTER) jid, groups = parse_roster_change_request(iq.query, iq.stanza) assertEquals('*****@*****.**', jid) assertEquals(set(('ladies',)), groups) acknowledge_iq(stream, iq.stanza) # we emit these as soon as the IQ is ack'd, so that we can indicate # group removal... q.expect('dbus-signal', signal='GroupsRemoved', args=[['people starting with A']]) q.expect('dbus-signal', signal='GroupsChanged', args=[[amy], [], ['people starting with A']]) q.expect('dbus-return', method='RemoveGroup') if it_worked: # ... although in fact this is what *actually* removes Amy from the # group send_roster_push(stream, '*****@*****.**', ['ladies']) else: # if the change didn't "stick", this message will revert it send_roster_push(stream, '*****@*****.**', ['ladies', 'people starting with A']) q.expect('dbus-signal', signal='GroupsCreated', args=[['people starting with A']]) q.expect('dbus-signal', signal='GroupsChanged', args=[[amy], ['people starting with A'], []]) sync_dbus(bus, q, conn) sync_stream(q, stream) check_contact_roster(conn, '*****@*****.**', ['ladies', 'people starting with A']) # sanity check: after all that, we expect Amy to be in group 'ladies' only sync_dbus(bus, q, conn) sync_stream(q, stream) assertEquals({ cs.CONN_IFACE_CONTACT_GROUPS + '/groups': ['ladies'], cs.CONN + '/contact-id': '*****@*****.**' }, conn.Contacts.GetContactAttributes([amy], [cs.CONN_IFACE_CONTACT_GROUPS], False)[amy]) # Rename group 'ladies' to 'girls' call_async(q, conn.ContactGroups, 'RenameGroup', 'ladies', 'girls') # Amy is added to 'girls' e = q.expect('stream-iq', iq_type='set', query_name='query', query_ns=ns.ROSTER) jid, groups = parse_roster_change_request(e.query, e.stanza) assertEquals('*****@*****.**', jid) assertEquals(set(['girls', 'ladies']), groups) send_roster_push(stream, '*****@*****.**', ['girls', 'ladies']) acknowledge_iq(stream, e.stanza) # Amy is removed from 'ladies' e = q.expect('stream-iq', iq_type='set', query_name='query', query_ns=ns.ROSTER) jid, groups = parse_roster_change_request(e.query, e.stanza) assertEquals('*****@*****.**', jid) assertEquals(set(['girls']), groups) send_roster_push(stream, '*****@*****.**', ['girls']) acknowledge_iq(stream, e.stanza) q.expect('dbus-return', method='RenameGroup') # check everything has been updated groups = conn.Properties.Get(cs.CONN_IFACE_CONTACT_GROUPS, 'Groups') assertContains('girls', groups) assertDoesNotContain('ladies', groups) contacts = conn.ContactList.GetContactListAttributes([cs.CONN_IFACE_CONTACT_GROUPS], False) assertEquals(['girls'], contacts[amy][cs.CONN_IFACE_CONTACT_GROUPS + '/groups'])
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, mc): simulated_cm = SimulatedConnectionManager(q, bus) ctl_dir = os.environ['MC_ACCOUNT_DIR'] old_key_file_name = os.path.join(ctl_dir, 'accounts.cfg') newer_key_file_name = os.path.join(os.environ['XDG_DATA_HOME'], 'telepathy', 'mission-control', 'accounts.cfg') # We do several scenarios in one MC run, to speed up testing a bit. scenarios = ('low', 'priority', 'masked', 'migration', 'absentcm') variant_file_names = {} low_prio_variant_file_names = {} account_paths = {} tails = {} for s in scenarios: variant_file_names[s] = os.path.join(os.environ['XDG_DATA_HOME'], 'telepathy', 'mission-control', 'fakecm-fakeprotocol-dontdivert%s_40example_2ecom0.account' % s) tails[s] = ('fakecm/fakeprotocol/dontdivert%s_40example_2ecom0' % s) account_paths[s] = cs.ACCOUNT_PATH_PREFIX + tails[s] low_prio_variant_file_names[s] = os.path.join( os.environ['XDG_DATA_DIRS'].split(':')[0], 'telepathy', 'mission-control', 'fakecm-fakeprotocol-dontdivert%s_40example_2ecom0.account' % s) try: os.makedirs(os.path.dirname(variant_file_names[s]), 0700) except OSError as e: if e.errno != errno.EEXIST: raise try: os.makedirs(os.path.dirname(low_prio_variant_file_names[s]), 0700) except OSError as e: if e.errno != errno.EEXIST: raise # This is deliberately a lower-priority location open(low_prio_variant_file_names['low'], 'w').write( """{ 'manager': <'fakecm'>, 'protocol': <'fakeprotocol'>, 'DisplayName': <'Account in a low-priority location'>, 'AutomaticPresence': <(uint32 2, 'available', '')>, 'Parameters': <{ 'account': <'*****@*****.**'>, 'password': <'password_in_variant_file'>, 'snakes': <uint32 42> }> } """) # This is in a lower-priority location and we don't know the # parameters' types yet open(low_prio_variant_file_names['migration'], 'w').write( """{ 'manager': <'fakecm'>, 'protocol': <'fakeprotocol'>, 'DisplayName': <'Account in a low-priority location with KeyFileParameters'>, 'AutomaticPresence': <(uint32 2, 'available', '')>, 'KeyFileParameters': <{ 'account': '*****@*****.**', 'password': '******', 'snakes': '42' }> } """) # This is in a lower-priority location, and we don't know the # parameters' types, and we can't learn them by asking the CM # because it isn't installed open(low_prio_variant_file_names['absentcm'], 'w').write( """{ 'manager': <'absentcm'>, 'protocol': <'absentprotocol'>, 'DisplayName': <'Account in a low-priority location with absent CM'>, 'AutomaticPresence': <(uint32 2, 'available', '')>, 'KeyFileParameters': <{ 'account': '*****@*****.**', 'password': '******', 'snakes': '42' }> } """) # This version of this account will be used open(variant_file_names['priority'], 'w').write("""{ 'manager': <'fakecm'>, 'protocol': <'fakeprotocol'>, 'DisplayName': <'Visible'>, 'AutomaticPresence': <(uint32 2, 'available', '')>, 'KeyFileParameters': <{'account': '*****@*****.**', 'password': '******', 'snakes': '42' }> } """) # This one won't, because it's "masked" by the higher-priority one open(low_prio_variant_file_names['priority'], 'w').write("""{ 'manager': <'fakecm'>, 'protocol': <'fakeprotocol'>, 'DisplayName': <'Hidden'>, 'Nickname': <'Hidden'>, 'AutomaticPresence': <(uint32 2, 'available', '')>, 'KeyFileParameters': <{'account': '*****@*****.**', 'password': '******', 'snakes': '42' }> } """) # This empty file is considered to "mask" the lower-priority one open(variant_file_names['masked'], 'w').write('') open(low_prio_variant_file_names['masked'], 'w').write("""{ 'manager': <'fakecm'>, 'protocol': <'fakeprotocol'>, 'AutomaticPresence': <(uint32 2, 'available', '')>, 'KeyFileParameters': <{'account': '*****@*****.**', 'password': '******', 'snakes': '42' }> } """) mc = MC(q, bus) account_manager, properties, interfaces = connect_to_mc(q, bus, mc) for s in scenarios: if s == 'masked': assertDoesNotContain(account_paths[s], properties['ValidAccounts']) assertDoesNotContain(account_paths[s], properties['InvalidAccounts']) elif s == 'absentcm': assertContains(account_paths[s], properties['InvalidAccounts']) assertDoesNotContain(account_paths[s], properties['ValidAccounts']) else: assertContains(account_paths[s], properties['ValidAccounts']) assertDoesNotContain(account_paths[s], properties['InvalidAccounts']) accounts = {} account_ifaces = {} for s in scenarios: if s != 'masked': accounts[s] = get_fakecm_account(bus, mc, account_paths[s]) account_ifaces[s] = dbus.Interface(accounts[s], cs.ACCOUNT) if s not in ('masked', 'absentcm'): # We can't get untyped parameters if we don't know what types # the CM gives them. assertEquals(42, accounts[s].Properties.Get(cs.ACCOUNT, 'Parameters')['snakes']) assertEquals(dbus.UInt32, type(accounts[s].Properties.Get(cs.ACCOUNT, 'Parameters')['snakes'])) # Files in lower-priority XDG locations aren't copied until something # actually changes, and they aren't deleted. if s == 'low': assert os.path.exists(low_prio_variant_file_names[s]) # Delete the password (only), like Empathy 3.0-3.4 do when migrating. # This results in the higher-priority file being written out. account_ifaces['low'].UpdateParameters({}, ['password']) q.expect('dbus-signal', path=account_paths['low'], signal='AccountPropertyChanged', interface=cs.ACCOUNT, predicate=(lambda e: 'Parameters' in e.args[0]), ) # Check the account has copied (not moved! XDG_DATA_DIRS are, # conceptually, read-only) 'low' from the old to the new name assert not os.path.exists(old_key_file_name) assert not os.path.exists(newer_key_file_name) assert os.path.exists(low_prio_variant_file_names['low']) assert os.path.exists(variant_file_names['low']) # test that priority works assertContains(account_paths["priority"], properties['ValidAccounts']) assertEquals('', accounts['priority'].Properties.Get(cs.ACCOUNT, 'Nickname')) assertEquals('Visible', accounts['priority'].Properties.Get(cs.ACCOUNT, 'DisplayName')) # test what happens when we delete an account that has a lower-priority # "other self": it becomes masked assert accounts['priority'].Remove() is None assert not os.path.exists(old_key_file_name) assert not os.path.exists(newer_key_file_name) assert os.path.exists(low_prio_variant_file_names['priority']) assert os.path.exists(variant_file_names['priority']) assert open(variant_file_names['priority'], 'r').read() == '' assertContains('password_in_variant_file', open(low_prio_variant_file_names['priority'], 'r').read()) # The masked account is still masked assert open(variant_file_names['masked'], 'r').read() == '' # Because the CM exists, we can work out the correct types # for the 'migration' account's parameters. This triggers a commit # even though nothing has conceptually changed, so we have the type # for later. The file is copied, not moved, because XDG_DATA_DIRS are, # conceptually, read-only. assert not os.path.exists(old_key_file_name) assert not os.path.exists(newer_key_file_name) assert os.path.exists(low_prio_variant_file_names['migration']) assert os.path.exists(variant_file_names['migration']) assertEquals("'password_in_variant_file'", account_store('get', 'variant-file', 'param-password', account=tails['migration'])) assertEquals("uint32 42", account_store('get', 'variant-file', 'param-snakes', account=tails['migration'])) # Setting the password still does the right thing. account_ifaces['migration'].UpdateParameters({'password': '******'}, []) q.expect('dbus-signal', path=account_paths['migration'], signal='AccountPropertyChanged', interface=cs.ACCOUNT, predicate=(lambda e: 'Parameters' in e.args[0]), ) assert not os.path.exists(old_key_file_name) assert not os.path.exists(newer_key_file_name) assert os.path.exists(low_prio_variant_file_names['migration']) assert os.path.exists(variant_file_names['migration']) assertEquals("'hello'", account_store('get', 'variant-file', 'param-password', account=tails['migration'])) # 'absentcm' is still only in the low-priority location: we can't # known the types of its parameters, so it doesn't get migrated. assert not os.path.exists(old_key_file_name) assert not os.path.exists(newer_key_file_name) assert os.path.exists(low_prio_variant_file_names['absentcm']) assert not os.path.exists(variant_file_names['absentcm'])