1# vim: set fileencoding=utf-8 :
2# Copyright © 2009 Nokia Corporation
3# Copyright © 2009–2011 Collabora Ltd.
4#
5# This library is free software; you can redistribute it and/or
6# modify it under the terms of the GNU Lesser General Public
7# License as published by the Free Software Foundation; either
8# version 2.1 of the License, or (at your option) any later version.
9#
10# This library is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13# Lesser General Public License for more details.
14#
15# You should have received a copy of the GNU Lesser General Public
16# License along with this library; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18# 02110-1301 USA
19
20import os
21import time
22
23import dbus
24import dbus.service
25
26from servicetest import EventPattern, tp_name_prefix, tp_path_prefix, \
27        call_async, assertEquals, assertSameSets
28from mctest import exec_test, SimulatedConnection, create_fakecm_account,\
29        SimulatedChannel
30import constants as cs
31
32def test(q, bus, mc, **kwargs):
33    cm_name_ref = dbus.service.BusName(
34            tp_name_prefix + '.ConnectionManager.fakecm', bus=bus)
35
36    # Create an account
37    params = dbus.Dictionary(
38        {"account": "someguy@example.com",
39         "password": "secrecy",
40         "nickname": "albinoblacksheep",
41         }, signature='sv')
42    (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
43
44    # Enable the account
45    account.Set(cs.ACCOUNT, 'Enabled', True,
46            dbus_interface=cs.PROPERTIES_IFACE)
47    q.expect('dbus-signal',
48            path=account.object_path,
49            signal='AccountPropertyChanged',
50            interface=cs.ACCOUNT)
51
52    # Go online
53    requested_presence = dbus.Struct((dbus.UInt32(2), dbus.String('brb'),
54                dbus.String('Be back soon!')))
55    account.Set(cs.ACCOUNT,
56            'RequestedPresence', requested_presence,
57            dbus_interface=cs.PROPERTIES_IFACE)
58
59    e = q.expect('dbus-method-call', method='RequestConnection',
60            args=['fakeprotocol', params],
61            destination=tp_name_prefix + '.ConnectionManager.fakecm',
62            path=tp_path_prefix + '/ConnectionManager/fakecm',
63            interface=tp_name_prefix + '.ConnectionManager',
64            handled=False)
65
66    conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol', '_',
67            'myself')
68
69    q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
70
71    # MC does some setup, including fetching the list of Channels
72
73    q.expect_many(
74            EventPattern('dbus-method-call',
75                interface=cs.PROPERTIES_IFACE, method='GetAll',
76                args=[cs.CONN_IFACE_REQUESTS],
77                path=conn.object_path, handled=True),
78            )
79
80    # MC calls GetStatus (maybe) and then Connect
81
82    q.expect('dbus-method-call', method='Connect',
83            path=conn.object_path, handled=True)
84
85    # Connect succeeds
86    conn.StatusChanged(cs.CONN_STATUS_CONNECTED, cs.CONN_STATUS_REASON_NONE)
87
88    # Assert that the NormalizedName is harvested from the Connection at some
89    # point
90    while 1:
91        e = q.expect('dbus-signal',
92                interface=cs.ACCOUNT, signal='AccountPropertyChanged',
93                path=account.object_path)
94        if 'NormalizedName' in e.args[0]:
95            assert e.args[0]['NormalizedName'] == 'myself', e.args
96            break
97
98    # Check the requested presence is online
99    properties = account.GetAll(cs.ACCOUNT,
100            dbus_interface=cs.PROPERTIES_IFACE)
101    assert properties is not None
102    assert properties.get('RequestedPresence') == requested_presence, \
103        properties.get('RequestedPresence')
104
105    # Set some parameters. They include setting account to \\, as a regression
106    # test for part of fd.o #28557.
107    call_async(q, account, 'UpdateParameters',
108            {
109                'account': r'\\',
110                'secret-mushroom': '/Amanita muscaria/',
111                'snakes': dbus.UInt32(42),
112                'com.example.Badgerable.Badgered': True,
113            },
114            [],
115            dbus_interface=cs.ACCOUNT)
116
117    set_call, ret, _ = q.expect_many(
118            EventPattern('dbus-method-call',
119                path=conn.object_path,
120                interface=cs.PROPERTIES_IFACE, method='Set',
121                args=['com.example.Badgerable', 'Badgered', True],
122                handled=False),
123            EventPattern('dbus-return',
124                method='UpdateParameters'),
125            EventPattern('dbus-signal',
126                path=account.object_path,
127                interface=cs.ACCOUNT, signal='AccountPropertyChanged',
128                args=[{'Parameters': {
129                    'account': r'\\',
130                    'com.example.Badgerable.Badgered': True,
131                    'password': 'secrecy',
132                    'nickname': 'albinoblacksheep',
133                    'secret-mushroom': '/Amanita muscaria/',
134                    'snakes': 42,
135                    }}]),
136            )
137
138    # the D-Bus property should be set instantly; the others will take effect
139    # on reconnection
140    not_yet = ret.value[0]
141    not_yet.sort()
142    assert not_yet == ['account', 'secret-mushroom', 'snakes'], not_yet
143
144    # Try to update 'account' to a value of the wrong type; MC should complain,
145    # without having changed the value of 'snakes'.
146    call_async(q, account, 'UpdateParameters',
147            { 'account': dbus.UInt32(39),
148              'snakes': dbus.UInt32(39),
149            },
150            [],
151            dbus_interface=cs.ACCOUNT)
152    q.expect('dbus-error', name=cs.INVALID_ARGUMENT)
153
154    props = account.Get(cs.ACCOUNT, 'Parameters',
155        dbus_interface=cs.PROPERTIES_IFACE)
156    assertEquals(42, props['snakes'])
157
158    # Try to update a parameter that doesn't exist; again, 'snakes' should not
159    # be changed.
160    call_async(q, account, 'UpdateParameters',
161            { 'accccccount': dbus.UInt32(39),
162              'snakes': dbus.UInt32(39),
163            },
164            [],
165            dbus_interface=cs.ACCOUNT)
166    q.expect('dbus-error', name=cs.INVALID_ARGUMENT)
167
168    props = account.Get(cs.ACCOUNT, 'Parameters',
169        dbus_interface=cs.PROPERTIES_IFACE)
170    assertEquals(42, props['snakes'])
171
172    # Unset some parameters, including a parameter which doesn't exist at all.
173    # The spec says that “If the given parameters […] do not exist at all, the
174    # account manager MUST accept this without error.”
175    call_async(q, account, 'UpdateParameters',
176            {},
177            ['nickname', 'com.example.Badgerable.Badgered', 'froufrou'],
178            dbus_interface=cs.ACCOUNT)
179
180    ret, _, _ = q.expect_many(
181            EventPattern('dbus-return',
182                method='UpdateParameters'),
183            EventPattern('dbus-signal',
184                path=account.object_path,
185                interface=cs.ACCOUNT, signal='AccountPropertyChanged',
186                args=[{'Parameters': {
187                    'account': r'\\',
188                    'password': 'secrecy',
189                    'secret-mushroom': '/Amanita muscaria/',
190                    'snakes': 42,
191                    }}]),
192            EventPattern('dbus-method-call',
193                path=conn.object_path,
194                interface=cs.PROPERTIES_IFACE, method='Set',
195                args=['com.example.Badgerable', 'Badgered', False],
196                handled=False),
197            )
198
199    # Because com.example.Badgerable.Badgered has a default value (namely
200    # False), unsetting that parameter should cause the default value to be set
201    # on the CM.
202    not_yet = ret.value[0]
203    assertEquals(['nickname'], not_yet)
204
205    # Set contrived-example to its default value; since there's been no
206    # practical change, we shouldn't be told we need to reconnect to apply it.
207    call_async(q, account, 'UpdateParameters',
208        { 'contrived-example': dbus.UInt32(5) }, [])
209    ret, _ = q.expect_many(
210            EventPattern('dbus-return', method='UpdateParameters'),
211            EventPattern('dbus-signal',
212                path=account.object_path,
213                interface=cs.ACCOUNT, signal='AccountPropertyChanged',
214                args=[{'Parameters': {
215                    'account': r'\\',
216                    'password': 'secrecy',
217                    'secret-mushroom': '/Amanita muscaria/',
218                    'snakes': 42,
219                    "contrived-example": 5,
220                    }}]),
221            )
222    not_yet = ret.value[0]
223    assertEquals([], not_yet)
224
225    # Unset contrived-example; again, MC should be smart enough to know we
226    # don't need to do anything.
227    call_async(q, account, 'UpdateParameters', {}, ['contrived-example'])
228    ret, _ = q.expect_many(
229            EventPattern('dbus-return', method='UpdateParameters'),
230            EventPattern('dbus-signal',
231                path=account.object_path,
232                interface=cs.ACCOUNT, signal='AccountPropertyChanged',
233                args=[{'Parameters': {
234                    'account': r'\\',
235                    'password': 'secrecy',
236                    'secret-mushroom': '/Amanita muscaria/',
237                    'snakes': 42,
238                    }}]),
239            )
240    not_yet = ret.value[0]
241    assertEquals([], not_yet)
242
243    # Unset contrived-example again; the spec decrees that “If the given
244    # parameters were not, in fact, stored, […] the account manager MUST accept
245    # this without error.”
246    call_async(q, account, 'UpdateParameters', {}, ['contrived-example'])
247    ret = q.expect('dbus-return', method='UpdateParameters')
248    not_yet = ret.value[0]
249    assertEquals([], not_yet)
250
251    cache_dir = os.environ['XDG_CACHE_HOME']
252
253    # Now that we're using GVariant-based storage, the backslashes aren't
254    # escaped.
255    assertEquals(r'\\',
256            kwargs['fake_accounts_service'].accounts
257            [account.object_path[len(cs.ACCOUNT_PATH_PREFIX):]]
258            [2]     # parameters of known type
259            ['account'])
260    assertEquals(None,
261            kwargs['fake_accounts_service'].accounts
262            [account.object_path[len(cs.ACCOUNT_PATH_PREFIX):]]
263            [3]     # parameters of unknown type
264            .get('account', None))
265
266if __name__ == '__main__':
267    exec_test(test, {}, pass_kwargs=True)
268