summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Laban <david.laban@collabora.co.uk>2010-12-03 16:38:38 +0000
committerDavid Laban <david.laban@collabora.co.uk>2011-01-26 17:16:33 +0000
commita9f18ec9ffe748c2078886cbc51c28babc3ce944 (patch)
treed64e4f8a808e910570f3984a301f41d6db24ce63
parent63dc72f4e45d249dc7cbccf9e721dc3ec07e2dc1 (diff)
initial import of outgoing-basics.py from gabble
-rw-r--r--tests/twisted/voip/outgoing-basics.py351
1 files changed, 351 insertions, 0 deletions
diff --git a/tests/twisted/voip/outgoing-basics.py b/tests/twisted/voip/outgoing-basics.py
new file mode 100644
index 0000000..d138f6d
--- /dev/null
+++ b/tests/twisted/voip/outgoing-basics.py
@@ -0,0 +1,351 @@
+"""
+Test basic outgoing call handling, using CreateChannel and all three variations
+of RequestChannel.
+"""
+
+import dbus
+from twisted.words.xish import xpath
+
+from gabbletest import exec_test
+from servicetest import (
+ make_channel_proxy, wrap_channel,
+ EventPattern, call_async,
+ assertEquals, assertContains, assertLength,
+ )
+import constants as cs
+from jingletest2 import JingleTest2, test_all_dialects
+
+# There are various deprecated APIs for requesting calls, documented at
+# <http://telepathy.freedesktop.org/wiki/Requesting StreamedMedia channels>.
+# These are ordered from most recent to most deprecated.
+CREATE = 0 # CreateChannel({TargetHandleType: Contact, TargetHandle: h});
+ # RequestStreams()
+REQUEST_ANONYMOUS = 1 # RequestChannel(HandleTypeNone, 0); RequestStreams()
+REQUEST_ANONYMOUS_AND_ADD = 2 # RequestChannel(HandleTypeNone, 0);
+ # AddMembers([h], ...); RequestStreams(h,...)
+REQUEST_NONYMOUS = 3 # RequestChannel(HandleTypeContact, h);
+ # RequestStreams(h, ...)
+
+def create(jp, q, bus, conn, stream, peer='foo@bar.com/Res'):
+ worker(jp, q, bus, conn, stream, CREATE, peer)
+
+def request_anonymous(jp, q, bus, conn, stream, peer='publish@foo.com/Res'):
+ worker(jp, q, bus, conn, stream, REQUEST_ANONYMOUS, peer)
+
+def request_anonymous_and_add(jp, q, bus, conn, stream,
+ peer='publish-subscribe@foo.com/Res'):
+ worker(jp, q, bus, conn, stream, REQUEST_ANONYMOUS_AND_ADD, peer)
+
+def request_nonymous(jp, q, bus, conn, stream, peer='subscribe@foo.com/Res'):
+ worker(jp, q, bus, conn, stream, REQUEST_NONYMOUS, peer)
+
+def worker(jp, q, bus, conn, stream, variant, peer):
+ jt2 = JingleTest2(jp, conn, q, stream, 'test@localhost', peer)
+ jt2.prepare(send_presence=True, send_roster=True)
+
+ self_handle = conn.GetSelfHandle()
+ remote_handle = conn.RequestHandles(1, [jt2.peer])[0]
+
+ if variant == REQUEST_NONYMOUS:
+ path = conn.RequestChannel(cs.CHANNEL_TYPE_STREAMED_MEDIA,
+ cs.HT_CONTACT, remote_handle, True)
+ elif variant == CREATE:
+ path = conn.Requests.CreateChannel({
+ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_STREAMED_MEDIA,
+ cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT,
+ cs.TARGET_HANDLE: remote_handle,
+ })[0]
+ else:
+ path = conn.RequestChannel(cs.CHANNEL_TYPE_STREAMED_MEDIA,
+ cs.HT_NONE, 0, True)
+
+ old_sig, new_sig = q.expect_many(
+ EventPattern('dbus-signal', signal='NewChannel',
+ predicate=lambda e: cs.CHANNEL_TYPE_CONTACT_LIST not in e.args),
+ EventPattern('dbus-signal', signal='NewChannels',
+ predicate=lambda e:
+ cs.CHANNEL_TYPE_CONTACT_LIST not in e.args[0][0][1].values()),
+ )
+
+ if variant == REQUEST_NONYMOUS or variant == CREATE:
+ assertEquals( [path, cs.CHANNEL_TYPE_STREAMED_MEDIA, cs.HT_CONTACT,
+ remote_handle, True], old_sig.args)
+ else:
+ assertEquals( [path, cs.CHANNEL_TYPE_STREAMED_MEDIA, cs.HT_NONE, 0,
+ True], old_sig.args)
+
+ assertLength(1, new_sig.args)
+ assertLength(1, new_sig.args[0]) # one channel
+ assertLength(2, new_sig.args[0][0]) # two struct members
+ emitted_props = new_sig.args[0][0][1]
+
+ assertEquals(
+ cs.CHANNEL_TYPE_STREAMED_MEDIA, emitted_props[cs.CHANNEL_TYPE])
+
+ if variant == REQUEST_NONYMOUS or variant == CREATE:
+ assertEquals(remote_handle, emitted_props[cs.TARGET_HANDLE])
+ assertEquals(cs.HT_CONTACT, emitted_props[cs.TARGET_HANDLE_TYPE])
+ assertEquals(jt2.peer_bare_jid, emitted_props[cs.TARGET_ID])
+ else:
+ assertEquals(0, emitted_props[cs.TARGET_HANDLE])
+ assertEquals(cs.HT_NONE, emitted_props[cs.TARGET_HANDLE_TYPE])
+ assertEquals('', emitted_props[cs.TARGET_ID])
+
+ assertEquals(True, emitted_props[cs.REQUESTED])
+ assertEquals(self_handle, emitted_props[cs.INITIATOR_HANDLE])
+ assertEquals('test@localhost', emitted_props[cs.INITIATOR_ID])
+
+ chan = wrap_channel(bus.get_object(conn.bus_name, path), 'StreamedMedia',
+ ['MediaSignalling'])
+
+ # Exercise basic Channel Properties
+ channel_props = chan.Properties.GetAll(cs.CHANNEL)
+
+ assertEquals(cs.CHANNEL_TYPE_STREAMED_MEDIA,
+ channel_props.get('ChannelType'))
+
+ if variant == REQUEST_NONYMOUS or variant == CREATE:
+ assertEquals(remote_handle, channel_props['TargetHandle'])
+ assertEquals(cs.HT_CONTACT, channel_props['TargetHandleType'])
+ assertEquals(jt2.peer_bare_jid, channel_props['TargetID'])
+ assertEquals((cs.HT_CONTACT, remote_handle), chan.GetHandle())
+ else:
+ assertEquals(0, channel_props['TargetHandle'])
+ assertEquals(cs.HT_NONE, channel_props['TargetHandleType'])
+ assertEquals('', channel_props['TargetID'])
+ assertEquals((cs.HT_NONE, 0), chan.GetHandle())
+
+ for interface in [
+ cs.CHANNEL_IFACE_GROUP, cs.CHANNEL_IFACE_MEDIA_SIGNALLING,
+ cs.TP_AWKWARD_PROPERTIES, cs.CHANNEL_IFACE_HOLD]:
+ assertContains(interface, channel_props['Interfaces'])
+
+ assertEquals(True, channel_props['Requested'])
+ assertEquals('test@localhost', channel_props['InitiatorID'])
+ assertEquals(conn.GetSelfHandle(), channel_props['InitiatorHandle'])
+
+ # Exercise Group Properties
+ group_props = chan.Properties.GetAll(cs.CHANNEL_IFACE_GROUP)
+
+ assertEquals([self_handle], group_props['Members'])
+ assertEquals([], group_props['LocalPendingMembers'])
+
+ if variant == REQUEST_NONYMOUS:
+ # In this variant, they're meant to be in RP even though we've sent
+ # nothing
+ assertEquals([remote_handle], group_props['RemotePendingMembers'])
+ else:
+ # For an anonymous channel, the peer isn't yet known; for a Create-d
+ # channel, the peer only appears in RP when we actually send them the
+ # session-initiate
+ assertEquals([], group_props['RemotePendingMembers'])
+
+ if variant == REQUEST_ANONYMOUS_AND_ADD:
+ # but we should be allowed to add the peer.
+ chan.Group.AddMembers([remote_handle], 'I love backwards compat')
+
+ base_flags = cs.GF_PROPERTIES | cs.GF_MESSAGE_REMOVE \
+ | cs.GF_MESSAGE_REJECT | cs.GF_MESSAGE_RESCIND
+
+ if variant == REQUEST_ANONYMOUS_AND_ADD or variant == REQUEST_ANONYMOUS:
+ expected_flags = base_flags | cs.GF_CAN_ADD
+ else:
+ expected_flags = base_flags
+ assertEquals(expected_flags, group_props['GroupFlags'])
+ assertEquals({}, group_props['HandleOwners'])
+
+ assertEquals([], chan.StreamedMedia.ListStreams())
+ streams = chan.StreamedMedia.RequestStreams(remote_handle,
+ [cs.MEDIA_STREAM_TYPE_AUDIO])
+ assertEquals(streams, chan.StreamedMedia.ListStreams())
+ assertLength(1, streams)
+
+ # streams[0][0] is the stream identifier, which in principle we can't
+ # make any assertion about (although in practice it's probably 1)
+
+ assertEquals((
+ remote_handle,
+ cs.MEDIA_STREAM_TYPE_AUDIO,
+ # We haven't connected yet
+ cs.MEDIA_STREAM_STATE_DISCONNECTED,
+ # In Gabble, requested streams start off bidirectional
+ cs.MEDIA_STREAM_DIRECTION_BIDIRECTIONAL,
+ 0),
+ streams[0][1:])
+
+ # S-E gets notified about new session handler, and calls Ready on it
+ e = q.expect('dbus-signal', signal='NewSessionHandler')
+ assert e.args[1] == 'rtp'
+
+ session_handler = make_channel_proxy(conn, e.args[0], 'Media.SessionHandler')
+ session_handler.Ready()
+
+ e = q.expect('dbus-signal', signal='NewStreamHandler')
+
+ stream_handler = make_channel_proxy(conn, e.args[0], 'Media.StreamHandler')
+
+ stream_handler.NewNativeCandidate("fake", jt2.get_remote_transports_dbus())
+ stream_handler.Ready(jt2.get_audio_codecs_dbus())
+ stream_handler.StreamState(cs.MEDIA_STREAM_STATE_CONNECTED)
+
+ sh_props = stream_handler.GetAll(
+ cs.STREAM_HANDLER, dbus_interface=dbus.PROPERTIES_IFACE)
+ assertEquals('gtalk-p2p', sh_props['NATTraversal'])
+ assertEquals(True, sh_props['CreatedLocally'])
+
+ if variant == CREATE:
+ # When we actually send XML to the peer, they should pop up in remote
+ # pending.
+ session_initiate, _ = q.expect_many(
+ EventPattern('stream-iq', iq_type='set', predicate=lambda e:
+ jp.match_jingle_action(e.query, 'session-initiate')),
+ EventPattern('dbus-signal', signal='MembersChanged',
+ args=["", [], [], [], [remote_handle], self_handle,
+ cs.GC_REASON_INVITED]),
+ )
+ else:
+ forbidden = []
+
+ if peer.split('/', 1)[0] in (
+ 'publish@foo.com', 'publish-subscribe@foo.com'):
+ forbidden = [EventPattern('stream-presence')]
+ q.forbid_events(forbidden)
+ else:
+ # we're calling someone not on our roster, so we'll send directed
+ # presence first
+ presence = q.expect('stream-presence')
+ assert (xpath.queryForNodes('/presence/c', presence.stanza)
+ is not None)
+ assert (xpath.queryForNodes(
+ '/presence/x[@xmlns="vcard-temp:x:update"]', presence.stanza)
+ is not None)
+
+ session_initiate = q.expect('stream-iq',
+ predicate=jp.action_predicate('session-initiate'))
+
+ if forbidden:
+ q.unforbid_events(forbidden)
+
+ jt2.parse_session_initiate(session_initiate.query)
+ stream.send(jp.xml(jp.ResultIq('test@localhost', session_initiate.stanza,
+ [])))
+
+ # Check the Group interface's properties again. Regardless of the call
+ # requesting API in use, the state should be the same here:
+ group_props = chan.Properties.GetAll(cs.CHANNEL_IFACE_GROUP)
+ assertContains('HandleOwners', group_props)
+ assertEquals([self_handle], group_props['Members'])
+ assertEquals([], group_props['LocalPendingMembers'])
+ assertEquals([remote_handle], group_props['RemotePendingMembers'])
+
+ if jp.dialect == 'gtalk-v0.4':
+ node = jp.SetIq(jt2.peer, jt2.jid, [
+ jp.Jingle(jt2.sid, jt2.peer, 'transport-accept', [
+ jp.TransportGoogleP2P() ]) ])
+ stream.send(jp.xml(node))
+
+ # FIXME: expect transport-info, then if we're gtalk3, send
+ # candidates, and check that gabble resends transport-info as
+ # candidates
+ jt2.accept()
+
+ q.expect_many(
+ EventPattern('stream-iq', iq_type='result'),
+ # Call accepted
+ EventPattern('dbus-signal', signal='MembersChanged',
+ args=['', [remote_handle], [], [], [], remote_handle,
+ cs.GC_REASON_NONE]),
+ )
+
+ # Time passes ... afterwards we close the chan
+
+ chan.Group.RemoveMembers([self_handle], 'closed')
+
+ # Make sure gabble sends proper terminate action
+ if jp.dialect.startswith('gtalk'):
+ terminate = EventPattern('stream-iq', predicate=lambda x:
+ xpath.queryForNodes("/iq/session[@type='terminate']",
+ x.stanza))
+ else:
+ terminate = EventPattern('stream-iq', predicate=lambda x:
+ xpath.queryForNodes("/iq/jingle[@action='session-terminate']",
+ x.stanza))
+
+ mc_event, _, _ = q.expect_many(
+ EventPattern('dbus-signal', signal='MembersChanged'),
+ EventPattern('dbus-signal', signal='Close'),
+ terminate,
+ )
+ # Check that we're the actor
+ assertEquals(self_handle, mc_event.args[5])
+
+def rccs(q, bus, conn, stream):
+ """
+ Tests that the connection's RequestableChannelClasses for StreamedMedia are
+ sane.
+ """
+ conn.Connect()
+
+ q.expect('dbus-signal', signal='StatusChanged',
+ args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED])
+
+ rccs = conn.Properties.Get(cs.CONN_IFACE_REQUESTS,
+ 'RequestableChannelClasses')
+
+ # Test Channel.Type.StreamedMedia
+ media_classes = [ rcc for rcc in rccs
+ if rcc[0][cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_STREAMED_MEDIA ]
+
+ assertLength(1, media_classes)
+
+ fixed, allowed = media_classes[0]
+
+ assertEquals(cs.HT_CONTACT, fixed[cs.TARGET_HANDLE_TYPE])
+
+ expected_allowed = [
+ cs.TARGET_ID, cs.TARGET_HANDLE,
+ cs.INITIAL_VIDEO, cs.INITIAL_AUDIO
+ ]
+
+ allowed.sort()
+ expected_allowed.sort()
+ assertEquals(expected_allowed, allowed)
+
+ # Test Channel.Type.Call
+ media_classes = [ rcc for rcc in rccs
+ if rcc[0][cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_CALL ]
+ assertLength(2, media_classes)
+
+ hts = []
+
+ for mc in media_classes:
+ fixed, allowed = mc
+
+ hts.append (fixed[cs.TARGET_HANDLE_TYPE])
+
+ expected_allowed = [
+ cs.TARGET_ID, cs.TARGET_HANDLE,
+ cs.CALL_INITIAL_VIDEO, cs.CALL_INITIAL_AUDIO,
+ cs.CALL_MUTABLE_CONTENTS
+ ]
+
+ allowed.sort()
+ expected_allowed.sort()
+ assertEquals(expected_allowed, allowed)
+
+ assertEquals(sorted([cs.HT_CONTACT, cs.HT_ROOM]), sorted(hts))
+
+if __name__ == '__main__':
+ exec_test(rccs)
+ test_all_dialects(create)
+ test_all_dialects(request_anonymous)
+ test_all_dialects(request_anonymous_and_add)
+ test_all_dialects(request_nonymous)
+ test_all_dialects(lambda j, q, b, c, s:
+ create(j, q, b, c, s, peer='foo@gw.bar.com'))
+ test_all_dialects(lambda j, q, b, c, s:
+ request_anonymous(j, q, b, c, s, peer='foo@gw.bar.com'))
+ test_all_dialects(lambda j, q, b, c, s:
+ request_anonymous_and_add(j, q, b, c, s, peer='foo@gw.bar.com'))
+ test_all_dialects(lambda j, q, b, c, s:
+ request_nonymous(j, q, b, c, s, peer='foo@gw.bar.com'))