1# vim: set fileencoding=utf-8 :
2# Copyright © 2011 Collabora Ltd.
3#
4# This library is free software; you can redistribute it and/or
5# modify it under the terms of the GNU Lesser General Public
6# License as published by the Free Software Foundation; either
7# version 2.1 of the License, or (at your option) any later version.
8#
9# This library is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12# Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public
15# License along with this library; if not, write to the Free Software
16# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17# 02110-1301 USA
18
19import dbus
20import dbus.service
21
22from servicetest import (
23    EventPattern, call_async, sync_dbus, assertEquals,
24)
25from mctest import (
26    exec_test, create_fakecm_account, expect_fakecm_connection,
27    SimulatedConnection,
28)
29import constants as cs
30
31import config
32
33def sync_connectivity_state(mc):
34    # We cannot simply use sync_dbus here, because nm-glib reports property
35    # changes in an idle (presumably to batch them all together). This is fine
36    # and all that, but means we have to find a way to make sure MC has flushed
37    # its idle queue to avoid this test being racy. (This isn't just
38    # theoretical: this test failed about once per five runs when it used sync_dbus.)
39    #
40    # The test-specific version of MC implements the 'BillyIdle' method, which
41    # returns from a low-priority idle.
42    mc.BillyIdle(dbus_interface='org.freedesktop.Telepathy.MissionControl5.RegressionTests')
43
44def test(q, bus, mc):
45    params = dbus.Dictionary(
46        {"account": "yum yum network manager",
47         "password": "boo boo connman (although your API *is* simpler)",
48        }, signature='sv')
49    (cm_name_ref, account) = create_fakecm_account(q, bus, mc, params)
50
51    # While we're not connected to the internet, RequestConnection should not
52    # be called.
53    request_connection_event = [
54        EventPattern('dbus-method-call', method='RequestConnection'),
55    ]
56    q.forbid_events(request_connection_event)
57
58    account.Properties.Set(cs.ACCOUNT, 'RequestedPresence',
59        (dbus.UInt32(cs.PRESENCE_TYPE_BUSY), 'busy', 'hlaghalgh'))
60
61    # Turn the account on, re-request an online presence, and even tell it to
62    # connect automatically, to check that none of these make it sign in.
63    call_async(q, account.Properties, 'Set', cs.ACCOUNT, 'Enabled', True)
64    q.expect('dbus-return', method='Set')
65    requested_presence = (dbus.UInt32(cs.PRESENCE_TYPE_BUSY), 'busy', 'gtfo')
66    call_async(q, account.Properties, 'Set', cs.ACCOUNT, 'RequestedPresence',
67        requested_presence)
68    q.expect('dbus-return', method='Set')
69    call_async(q, account.Properties, 'Set', cs.ACCOUNT, 'ConnectAutomatically',
70        True)
71    q.expect('dbus-return', method='Set')
72    # (but actually let's turn ConnectAutomatically off: we want to check that
73    # MC continues to try to apply RequestedPresence if the network connection
74    # goes away and comes back again, regardless of this setting)
75    call_async(q, account.Properties, 'Set', cs.ACCOUNT, 'ConnectAutomatically',
76        False)
77    q.expect('dbus-return', method='Set')
78
79    sync_dbus(bus, q, mc)
80    q.unforbid_events(request_connection_event)
81
82    # Okay, I'm satisfied. Turn the network on.
83    mc.connectivity.go_online()
84
85    expect_fakecm_connection(q, bus, mc, account, params, has_presence=True,
86        expect_before_connect=[
87            EventPattern('dbus-method-call', method='SetPresence',
88                args=list(requested_presence[1:])),
89        ])
90
91    if config.HAVE_NM:
92        # If NetworkManager tells us that it is going to disconnect soon,
93        # the connection should be banished. GNetworkMonitor can't tell us
94        # that; either it's online or it isn't.
95        mc.connectivity.go_indeterminate()
96        q.expect('dbus-method-call', method='Disconnect')
97
98        mc.connectivity.go_offline()
99        sync_connectivity_state(mc)
100
101        # When we turn the network back on, MC should try to sign us back on.
102        # In the process, our RequestedPresence should not have been
103        # trampled on, as below.
104        mc.connectivity.go_online()
105        expect_fakecm_connection(q, bus, mc, account, params,
106            has_presence=True,
107            expect_before_connect=[
108                EventPattern('dbus-method-call', method='SetPresence',
109                    args=list(requested_presence[1:])),
110            ])
111
112        assertEquals(requested_presence,
113            account.Properties.Get(cs.ACCOUNT, 'RequestedPresence'))
114
115    # If we turn the network off, the connection should be banished.
116    mc.connectivity.go_offline()
117    q.expect('dbus-method-call', method='Disconnect')
118
119    # When we turn the network back on, MC should try to sign us back on.
120    mc.connectivity.go_online()
121    e = q.expect('dbus-method-call', method='RequestConnection')
122
123    # In the process, our RequestedPresence should not have been trampled on.
124    # (Historically, MC would replace it with the AutomaticPresence, but that
125    # behaviour was unexpected: if the user explicitly set a status or message,
126    # why should the network connection cutting out and coming back up cause
127    # that to be lost?)
128    assertEquals(requested_presence,
129        account.Properties.Get(cs.ACCOUNT, 'RequestedPresence'))
130
131    # But if we get disconnected before RequestConnection returns, MC should
132    # clean up the new connection when it does, rather than trying to sign it
133    # in.
134    connect_event = [ EventPattern('dbus-method-call', method='Connect'), ]
135    q.forbid_events(connect_event)
136
137    mc.connectivity.go_offline()
138    # Make sure that MC has noticed that the network connection has gone away.
139    sync_connectivity_state(mc)
140
141    conn = SimulatedConnection(q, bus, 'fakecm', 'fakeprotocol',
142        account.object_path.split('/')[-1], 'myself')
143    q.dbus_return(e.message, conn.bus_name, conn.object_path, signature='so')
144
145    q.expect('dbus-method-call', method='Disconnect')
146
147    # So now the user gives up and sets their RequestedPresence to offline.
148    # Because this account does not ConnectAutomatically, if the network
149    # connection comes back up the account should not be brought back online.
150    q.forbid_events(request_connection_event)
151    account.Properties.Set(cs.ACCOUNT, 'RequestedPresence',
152        (dbus.UInt32(cs.PRESENCE_TYPE_OFFLINE), 'offline', ''))
153    mc.connectivity.go_online()
154    # Make sure MC has noticed that the network connection has come back.
155    sync_connectivity_state(mc)
156
157if __name__ == '__main__':
158    exec_test(test, initially_online=False)
159