1# vim: set fileencoding=utf-8
2# Tests publishing an avatar in MUCs, and getting tokens for ourselves and
3# others. Serves as a regression test for
4# <https://bugs.freedesktop.org/show_bug.cgi?id=32017>, where our MUC-specific
5# self handle would have an empty avatar token even though we're publishing our
6# avatar on the wire correctly.
7
8import hashlib
9from servicetest import (
10    call_async, EventPattern, assertEquals, assertLength, sync_dbus,
11    wrap_channel,
12    )
13from gabbletest import (
14    exec_test, expect_and_handle_get_vcard, expect_and_handle_set_vcard,
15    make_muc_presence, elem,
16    )
17from twisted.words.xish import xpath
18import ns
19import constants as cs
20from mucutil import try_to_join_muc
21
22AVATAR_1_DATA = 'nyan'
23AVATAR_1_SHA1 = hashlib.sha1(AVATAR_1_DATA).hexdigest()
24AVATAR_1_MIME_TYPE = 'image/x-pop-tart'
25
26AVATAR_2_DATA = 'NYAN'
27AVATAR_2_SHA1 = hashlib.sha1(AVATAR_2_DATA).hexdigest()
28AVATAR_2_MIME_TYPE = 'image/x-pop-tart'
29
30MUC = 'taco-dog@nyan.cat'
31
32def extract_hash_from_presence(stanza):
33    return xpath.queryForString(
34        '/presence/x[@xmlns="%s"]/photo' % ns.VCARD_TEMP_UPDATE,
35        stanza)
36
37def test(q, bus, conn, stream):
38    self_handle = conn.GetSelfHandle()
39
40    # When Gabble initially requests its avatar from the server, it discovers
41    # it has none.
42    expect_and_handle_get_vcard(q, stream)
43    handle, signalled_token = q.expect('dbus-signal', signal='AvatarUpdated').args
44
45    assertEquals(self_handle, handle)
46    assertEquals('', signalled_token)
47
48    # The user sets an avatar.
49    call_async(q, conn.Avatars, 'SetAvatar', AVATAR_1_DATA, AVATAR_1_MIME_TYPE)
50    expect_and_handle_get_vcard(q, stream)
51    expect_and_handle_set_vcard(q, stream)
52
53    # It's signalled on D-Bus …
54    set_ret, avatar_updated = q.expect_many(
55        EventPattern('dbus-return', method='SetAvatar'),
56        EventPattern('dbus-signal', signal='AvatarUpdated'),
57        )
58
59    returned_token, = set_ret.value
60    handle, signalled_token = avatar_updated.args
61
62    assertEquals(self_handle, handle)
63    assertEquals(returned_token, signalled_token)
64
65    # … and also on XMPP.
66    broadcast = q.expect('stream-presence', to=None)
67    broadcast_hash = extract_hash_from_presence(broadcast.stanza)
68    assertEquals(AVATAR_1_SHA1, broadcast_hash)
69
70    # If applications ask Gabble for information about the user's own avatar,
71    # it should be able to answer. (Strictly speaking, expecting Gabble to know
72    # the avatar data is risky because Gabble discards cached vCards after a
73    # while, but we happen to know it takes 20 seconds or so for that to
74    # happen.)
75    known = conn.Avatars.GetKnownAvatarTokens([self_handle])
76    assertEquals({self_handle: signalled_token}, known)
77
78    conn.Avatars.RequestAvatars([self_handle])
79    retrieved = q.expect('dbus-signal', signal='AvatarRetrieved')
80    handle, token, data, mime_type = retrieved.args
81    assertEquals(self_handle, handle)
82    assertEquals(signalled_token, token)
83    assertEquals(AVATAR_1_DATA, data)
84    assertEquals(AVATAR_1_MIME_TYPE, mime_type)
85
86    # Well, that was quite easy. How about we join a MUC? XEP-0153 §4.1 says:
87    #     If a client supports the protocol defined herein, it […] SHOULD
88    #     also include the update child in directed presence stanzas (e.g.,
89    #     directed presence sent when joining Multi-User Chat [5] rooms).
90    #         — http://xmpp.org/extensions/xep-0153.html#bizrules-presence
91    join_event = try_to_join_muc(q, bus, conn, stream, MUC)
92    directed_hash = extract_hash_from_presence(join_event.stanza)
93    assertEquals(AVATAR_1_SHA1, directed_hash)
94
95    # There are two others in the MUC: fredrik has no avatar, wendy has an
96    # avatar. We, of course, have our own avatar.
97    stream.send(make_muc_presence('none', 'participant', MUC, 'fredrik'))
98    stream.send(make_muc_presence('none', 'participant', MUC, 'wendy',
99          photo=AVATAR_2_SHA1))
100    stream.send(make_muc_presence('owner', 'moderator', MUC, 'test',
101          photo=AVATAR_1_SHA1))
102
103    path, _ = q.expect('dbus-return', method='CreateChannel').value
104    chan = wrap_channel(bus.get_object(conn.bus_name, path), 'Text',
105        ['Messages'])
106
107    members = chan.Properties.Get(cs.CHANNEL_IFACE_GROUP, 'Members')
108    assertLength(3, members)
109
110    fredrik, wendy, muc_self_handle = conn.RequestHandles(cs.HT_CONTACT,
111        ['%s/%s' % (MUC, x) for x in ["fredrik", "wendy", "test"]])
112
113    known = conn.Avatars.GetKnownAvatarTokens(members)
114    # <https://bugs.freedesktop.org/show_bug.cgi?id=32017>: this assertion
115    # failed, the MUC self handle's token was the empty string.
116    assertEquals(AVATAR_1_SHA1, known[muc_self_handle])
117    assertEquals(AVATAR_2_SHA1, known[wendy])
118    assertEquals('', known[fredrik])
119
120    # 'k, cool. Wendy loves our avatar and switches to it.
121    stream.send(make_muc_presence('none', 'participant', MUC, 'wendy',
122          photo=AVATAR_1_SHA1))
123    # Okay this is technically assuming that we just expose the SHA1 sums
124    # directly which is not guaranteed … but we do.
125    q.expect('dbus-signal', signal='AvatarUpdated',
126        args=[wendy, AVATAR_1_SHA1])
127
128    # Fredrik switches too.
129    stream.send(make_muc_presence('none', 'participant', MUC, 'fredrik',
130          photo=AVATAR_1_SHA1))
131    q.expect('dbus-signal', signal='AvatarUpdated',
132        args=[fredrik, AVATAR_1_SHA1])
133
134    # And we switch to some other avatar. Gabble should update its vCard, and
135    # then update its MUC presence (which the test, acting as the MUC server,
136    # must echo).
137    call_async(q, conn.Avatars, 'SetAvatar', AVATAR_2_DATA, AVATAR_2_MIME_TYPE)
138    expect_and_handle_get_vcard(q, stream)
139    expect_and_handle_set_vcard(q, stream)
140
141    muc_presence = q.expect('stream-presence', to=('%s/test' % MUC))
142    directed_hash = extract_hash_from_presence(muc_presence.stanza)
143    stream.send(make_muc_presence('owner', 'moderator', MUC, 'test',
144          photo=directed_hash))
145
146    # Gabble should signal an avatar update for both our global self-handle and
147    # our MUC self-handle. (The first of these of course does not need to wait
148    # for the MUC server to echo our presence.)
149    q.expect_many(
150        EventPattern('dbus-signal', signal='AvatarUpdated',
151            args=[self_handle, AVATAR_2_SHA1]),
152        EventPattern('dbus-signal', signal='AvatarUpdated',
153            args=[muc_self_handle, AVATAR_2_SHA1]),
154        )
155
156if __name__ == '__main__':
157    exec_test(test)
158