Only run the rest of WiFi Radar if the configuration was created
[wifi-radar.git] / test / unit / connections.py
blob55053468f270a1ff1224f8676022e197744e1a91
1 # test.connections - tests for connection manager
3 # Part of WiFi Radar: A utility for managing WiFi profiles on GNU/Linux.
5 # Copyright (C) 2014 Sean Robinson <robinson@tuxfamily.org>
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; version 2 of the License.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License in LICENSE.GPL for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to:
18 # Free Software Foundation, Inc.
19 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 from __future__ import unicode_literals
25 from multiprocessing import Pipe
26 from signal import SIGTERM
27 from subprocess import CalledProcessError
28 from threading import Thread
29 import unittest
31 import mock
33 import wifiradar.connections
34 from wifiradar.config import ConfigManager
35 from wifiradar.misc import DeviceError, get_new_profile
36 from wifiradar.pubsub import Message
39 class TestScanner(unittest.TestCase):
40 @mock.patch('wifiradar.connections.Popen')
41 def test_scanner(self, mock_popen):
42 """ Test scanner function. """
43 # Prepare the profile information to test against mock scan data.
44 profiles = list()
45 profile = get_new_profile()
46 profile.update({'protocol': 'bg', 'bssid': '10:15:54:9E:2E:AA',
47 'encrypted': True, 'signal': '-58', 'essid': 'startingenlivening',
48 'channel': '2', 'mode': 'Master'})
49 profiles.append(profile)
50 profile = get_new_profile()
51 profile.update({'protocol': 'bg', 'bssid': 'B2:B0:03:1A:8F:6B',
52 'encrypted': False, 'signal': '-55', 'essid': 'filtersgenuinely',
53 'channel': '3', 'mode': 'Master'})
54 profiles.append(profile)
55 profile = get_new_profile()
56 profile.update({'protocol': 'bg', 'bssid': '10:A8:08:4C:18:26',
57 'encrypted': False, 'signal': '-54', 'essid': 'tractorsenslave',
58 'channel': '11', 'mode': 'Managed'})
59 profiles.append(profile)
61 # Read mock scan data from file.
62 mock_popen.return_value.stdout = open('./test/data/mock-scan-001.txt', 'r')
64 # Create control Pipe
65 a, b = Pipe()
66 test_thread = Thread(None, wifiradar.connections.scanner, args=(mock.Mock(), b))
67 test_thread.start()
68 for profile in profiles:
69 # assert on poll() ensures a timeout if any expected msgs are not sent.
70 self.assertTrue(a.poll(5))
71 msg = a.recv()
72 self.assertEqual('ACCESSPOINT', msg.topic)
73 self.assertEqual(profile, msg.details)
74 a.send(Message('EXIT', ''))
75 test_thread.join()
78 class TestConnectionManager(unittest.TestCase):
79 def setUp(self):
80 self.conn_manager = wifiradar.connections.ConnectionManager(mock.Mock())
81 self.mock_config = mock.Mock()
82 self.conn_manager.config = self.mock_config
84 def test_set_config(self):
85 """ Test set_config method. """
86 self.assertRaises(TypeError, self.conn_manager.set_config, None)
87 self.assertEqual(None, self.conn_manager.set_config(ConfigManager()))
89 @mock.patch('wifiradar.connections.shellcmd')
90 def test__run_script(self, mock_shellcmd):
91 """ Test _run_script method. """
92 script_name = 'con_script'
93 device = 'wlan0'
94 ip = '192.168.1.1'
95 essid = 'WinterPalace'
96 bssid = '01:02:03:04:05:06'
97 profile = {
98 'con_script': 'mysterious_bash_command',
99 'essid': essid,
100 'bssid': bssid,
101 'use_wpa': False,
102 'key': '',
103 'security': ''}
104 self.conn_manager.get_current_ip = mock.Mock(return_value=ip)
105 self.conn_manager.get_current_essid = mock.Mock(return_value=essid)
106 self.conn_manager.get_current_bssid = mock.Mock(return_value=bssid)
108 self.assertEqual(None,
109 self.conn_manager._run_script(script_name, profile, device))
111 custom_env = {
112 "WIFIRADAR_IP": ip,
113 "WIFIRADAR_ESSID": essid,
114 "WIFIRADAR_BSSID": bssid,
115 "WIFIRADAR_PROFILE": '{}:{}'.format(essid, bssid),
116 "WIFIRADAR_ENCMODE": 'none',
117 "WIFIRADAR_SECMODE": '',
118 "WIFIRADAR_IF": device}
119 mock_shellcmd.assert_called_with(['mysterious_bash_command'],
120 custom_env)
122 @mock.patch('wifiradar.connections.shellcmd')
123 def test__prepare_nic(self, mock_shellcmd):
124 """ Test _prepare_nic method. """
125 self.mock_config.get_opt.return_value = 'mock_iwconfig'
126 self.mock_config.get_opt_as_bool.return_value = True
127 essid = 'WinterPalace'
128 bssid = '01:02:03:04:05:06'
129 profile = {
130 'essid': essid,
131 'bssid': bssid,
132 'key': '',
133 'mode': 'managed',
134 'channel': 11}
135 device = 'wlan0'
136 self.assertEqual(None,
137 self.conn_manager._prepare_nic(profile, device))
138 mock_shellcmd.assert_called_with(['mock_iwconfig', device,
139 'essid', "'{}'".format(essid), 'key', 'off',
140 'mode', 'managed', 'channel', 11, 'ap', bssid, 'commit'])
142 # Test failure.
143 self.mock_config.get_opt.return_value = 'mock_iwconfig'
144 self.mock_config.get_opt_as_bool.return_value = True
145 mock_shellcmd.side_effect = CalledProcessError(250, 'mock_iwconfig')
146 self.assertRaises(DeviceError,
147 self.conn_manager._prepare_nic, profile, device)
149 @mock.patch('__builtin__.open', )
150 @mock.patch('os.kill')
151 @mock.patch('os.access')
152 @mock.patch('wifiradar.connections.shellcmd')
153 def test__stop_dhcp(self, mock_shellcmd, mock_access, mock_kill, mock_open):
154 """ Test _stop_dhcp method. """
155 dhcp_cmd = 'dhcpcd'
156 kill_args = '-k -d'
157 kill_args_list = ['-k', '-d']
158 self.mock_config.get_opt.side_effect = ['mock_pidfile', kill_args,
159 dhcp_cmd, kill_args]
160 mock_access.return_value = True
161 device = 'wlan0'
163 self.assertEqual(None, self.conn_manager._stop_dhcp(device))
164 call_list = [dhcp_cmd]
165 call_list.extend(kill_args_list)
166 call_list.append(device)
167 mock_shellcmd.assert_called_with(call_list)
169 self.mock_config.get_opt.side_effect = ['mock_pidfile', '',
170 dhcp_cmd, '', 'mock_pidfile']
171 PID = 42
172 mock_open.return_value.readline.return_value = PID
173 self.assertEqual(None, self.conn_manager._stop_dhcp(device))
174 mock_kill.assert_called_with(PID, SIGTERM)
176 @mock.patch('wifiradar.connections.Popen')
177 def test__start_dhcp(self, mock_popen):
178 """ Test _start_dhcp method. """
179 dhcp_cmd = 'dhcpcd'
180 dhcp_args = '-t %(timeout)s -w'
181 dhcp_args_list = ['-t', '%(timeout)s', '-w']
182 dhcp_timeout = 1
183 device = 'wlan0'
184 self.mock_config.get_opt.side_effect = [dhcp_cmd, dhcp_args]
185 self.mock_config.get_opt_as_int.return_value = dhcp_timeout
186 self.conn_manager.get_current_ip = mock.Mock(return_value='0.0.0.0')
187 self.assertEqual(None, self.conn_manager._start_dhcp(device))
188 call_list = [dhcp_cmd]
189 call_list.extend(dhcp_args_list)
190 call_list.append(device)
191 mock_popen.assert_called_with(call_list, stderr=None, stdout=None)
192 # Test missing DHCP command.
193 self.mock_config.get_opt.side_effect = [dhcp_cmd, dhcp_args]
194 mock_popen.side_effect = OSError(2, 'No such file or directory')
195 self.assertEqual(None, self.conn_manager._start_dhcp(device))
197 @mock.patch('__builtin__.open', )
198 @mock.patch('os.kill')
199 @mock.patch('os.access')
200 @mock.patch('wifiradar.connections.shellcmd')
201 def test__stop_wpa(self, mock_shellcmd, mock_access, mock_kill, mock_open):
202 """ Test _stop_wpa method. """
203 kill_cmd = ''
204 self.mock_config.get_opt.side_effect = ['mock_pidfile', kill_cmd,
205 'mock_pidfile']
206 mock_access.return_value = True
208 PID = 42
209 mock_open.return_value.readline.return_value = PID
210 self.assertEqual(None, self.conn_manager._stop_wpa())
211 mock_kill.assert_called_with(PID, SIGTERM)
213 @mock.patch('wifiradar.connections.shellcmd')
214 def test__start_wpa(self, mock_shellcmd):
215 """ Test _start_wpa method. """
216 wpa_cmd = 'mock_wpa'
217 wpa_args = '-B -i %(interface)s -c %(configuration)s'
218 wpa_args_list = ['-B', '-i', '%(interface)s', '-c', '%(configuration)s']
219 self.mock_config.get_opt.side_effect = [wpa_cmd, wpa_args]
220 self.assertEqual(None,
221 self.conn_manager._start_wpa())
222 call_list = [wpa_cmd]
223 call_list.extend(wpa_args_list)
224 mock_shellcmd.assert_called_with(call_list)
226 @mock.patch('__builtin__.open', )
227 @mock.patch('wifiradar.connections.shellcmd')
228 def test__start_manual_network(self, mock_shellcmd, mock_open):
229 """ Test _start_manual_network method. """
230 ifconfig = 'mock_ifconfig'
231 route = 'mock_route'
232 ip = '192.168.1.100'
233 netmask = '192.168.1.255'
234 gateway = '192.168.1.1'
235 domain = 'winterpalace.org'
236 dns1 = '192.168.1.200'
237 dns2 = '192.168.1.201'
238 profile = {
239 'ip': ip,
240 'netmask': netmask,
241 'gateway': gateway,
242 'domain': domain,
243 'dns1': dns1,
244 'dns2': dns2}
245 device = 'wlan0'
246 self.mock_config.get_opt.side_effect = [ifconfig, ifconfig, route]
248 self.assertEqual(None,
249 self.conn_manager._start_manual_network(profile, device))
250 mock_shellcmd.assert_any_call([ifconfig, device, 'down'])
251 mock_shellcmd.assert_any_call([ifconfig, device, ip,
252 'netmask', netmask])
253 mock_shellcmd.assert_any_call([route, 'add', 'default',
254 'gw', gateway])
255 # Test mock-writing the resolv file.
256 mock_open.assert_called_with('/etc/resolv.conf', 'w')
257 # Check that resolv_file.write was called with the proper args.
258 mock_open.return_value.__enter__.return_value.write.assert_called_with(
259 'domain {}\nnameserver {}\nnameserver {}\n'.format(domain, dns1, dns2))
261 def test_connect(self):
262 """ Test connect method. """
263 profile = {
264 'essid': 'WinterPalace',
265 'bssid': '01:02:03:04:05:06',
266 'use_wpa': True,
267 'use_dhcp': True}
268 device = 'wlan0'
269 self.mock_config.get_network_device.return_value = device
271 self.conn_manager._run_script = mock.Mock(name='_run_script')
272 self.conn_manager._prepare_nic = mock.Mock(name='_prepare_nic')
273 self.conn_manager._stop_dhcp = mock.Mock(name='_stop_dhcp')
274 self.conn_manager._start_dhcp = mock.Mock(name='_start_dhcp')
275 self.conn_manager._stop_wpa = mock.Mock(name='_stop_wpa')
276 self.conn_manager._start_wpa = mock.Mock(name='_start_wpa')
277 self.conn_manager._start_manual_network = mock.Mock(
278 name='_start_manual_network')
280 # Test DHCP and WPA network configuration.
281 self.assertEqual(None, self.conn_manager.connect(profile))
282 self.conn_manager._run_script.assert_any_call('con_prescript',
283 profile, device)
284 self.conn_manager._run_script.assert_any_call('con_postscript',
285 profile, device)
286 self.conn_manager._prepare_nic.assert_called_with(profile, device)
287 self.conn_manager._stop_dhcp.assert_called_with(device)
288 self.conn_manager._start_dhcp.assert_called_with(device)
289 self.conn_manager._stop_wpa.assert_called_with()
290 self.conn_manager._start_wpa.assert_called_with()
292 # Test manual network configuration.
293 profile['use_dhcp'] = False
294 self.assertEqual(None, self.conn_manager.connect(profile))
295 self.conn_manager._start_manual_network.assert_called_with(profile, device)
297 @mock.patch('wifiradar.connections.shellcmd')
298 def test_disconnect(self, mock_shellcmd):
299 """ Test disconnect method. """
300 iwconfig = 'mock_iwconfig'
301 ifconfig = 'mock_ifconfig'
302 profile = {}
303 device = 'wlan0'
304 self.mock_config.get_network_device.return_value = device
305 self.mock_config.get_opt.side_effect = [iwconfig, ifconfig]
306 self.conn_manager._run_script = mock.Mock(name='_run_script')
307 self.conn_manager._stop_dhcp = mock.Mock(name='_stop_dhcp')
308 self.conn_manager._stop_wpa = mock.Mock(name='_stop_wpa')
309 self.conn_manager.if_change = mock.Mock(name='if_change')
311 self.assertEqual(None, self.conn_manager.disconnect(profile))
312 self.conn_manager._run_script.assert_any_call('dis_prescript',
313 profile, device)
314 self.conn_manager._run_script.assert_any_call('dis_postscript',
315 profile, device)
316 self.conn_manager._stop_dhcp.assert_called_with(device)
317 self.conn_manager._stop_wpa.assert_called_with()
319 mock_shellcmd.assert_any_call([iwconfig, device,
320 'essid', 'any', 'key', 'off', 'mode', 'managed',
321 'channel', 'auto', 'ap', 'off'])
322 mock_shellcmd.assert_any_call([ifconfig, device, '0.0.0.0'])
324 @mock.patch('wifiradar.connections.Popen')
325 def test_if_change(self, mock_popen):
326 """ Test if_change method. """
327 self.mock_config.get_opt.return_value = 'mock_ifconfig'
328 self.mock_config.get_network_device.return_value = 'wlan0'
330 # Test normal operation.
331 self.assertEqual(None, self.conn_manager.if_change('up'))
332 self.assertEqual(None, self.conn_manager.if_change('down'))
333 self.assertEqual(None, self.conn_manager.if_change('UP'))
334 self.assertEqual(None, self.conn_manager.if_change('DOWN'))
336 # Test unknown state.
337 self.assertRaises(ValueError, self.conn_manager.if_change, 'sideways')
339 # Test failed state change with found ifconfig.
340 mock_popen_instance = mock.MagicMock()
341 mock_popen_instance.stdout.__iter__.return_value = [
342 'wlan0: ERROR while getting interface flags: No such device']
343 mock_popen.return_value = mock_popen_instance
344 self.assertRaises(DeviceError, self.conn_manager.if_change, 'up')
346 # Test OSError.
347 mock_popen.side_effect = OSError(2, 'No such file or directory')
348 self.assertRaises(OSError, self.conn_manager.if_change, 'up')
350 @mock.patch('wifiradar.connections.Popen')
351 def test_get_current_ip(self, mock_popen):
352 """ Test get_current_ip method. """
353 self.mock_config.get_opt.return_value = 'mock_ifconfig'
354 self.mock_config.get_network_device.return_value = 'wlan0'
356 # Test connected and with an IP address.
357 mock_popen_instance = mock.Mock()
358 mock_popen_instance.stdout.read.return_value = \
359 'wlan0: flags=4098<BROADCAST,MULTICAST> mtu 1500\n' + \
360 'inet addr:192.168.1.1 netmask 255.255.255.0 broadcast 192.168.1.255\n' + \
361 'ether 00:00:00:00:00:00 txqueuelen 1000 (Ethernet)\n' + \
362 'RX packets 246 bytes 259631 (253.5 KiB)\n' + \
363 'RX errors 0 dropped 1 overruns 0 frame 0\n' + \
364 'TX packets 123 bytes 140216 (136.9 KiB)\n' + \
365 'TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0\n'
366 mock_popen.return_value = mock_popen_instance
367 self.assertEqual('192.168.1.1', self.conn_manager.get_current_ip())
369 # Test not connected and without an IP address.
370 mock_popen_instance.stdout.read.return_value = \
371 'wlan0: flags=4098<BROADCAST,MULTICAST> mtu 1500\n' + \
372 'ether 00:00:00:00:00:00 txqueuelen 1000 (Ethernet)\n' + \
373 'RX packets 246 bytes 259631 (253.5 KiB)\n' + \
374 'RX errors 0 dropped 1 overruns 0 frame 0\n' + \
375 'TX packets 123 bytes 140216 (136.9 KiB)\n' + \
376 'TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0\n'
377 mock_popen.return_value = mock_popen_instance
378 self.assertEqual(None, self.conn_manager.get_current_ip())
380 @mock.patch('wifiradar.connections.Popen')
381 def test_get_current_essid(self, mock_popen):
382 """ Test get_current_essid method. """
383 self.mock_config.get_opt.return_value = 'mock_iwconfig'
384 self.mock_config.get_network_device.return_value = 'wlan0'
386 # Test associated interface.
387 mock_popen_instance = mock.Mock()
388 mock_popen_instance.stdout.read.return_value = \
389 'wlan0 IEEE 802.11abgn ESSID:"WinterPalace"\n' + \
390 ' Mode:Managed Frequency:5.26 GHz Access Point: 00:00:00:00:00:00\n' + \
391 ' Tx-Power=15 dBm\n' + \
392 ' Retry long limit:7 RTS thr:off Fragment thr:off\n' + \
393 ' Power Management:off\n'
394 mock_popen.return_value = mock_popen_instance
395 self.assertEqual('WinterPalace', self.conn_manager.get_current_essid())
397 # Test not associated interface.
398 mock_popen_instance = mock.Mock()
399 mock_popen_instance.stdout.read.return_value = \
400 'wlan0 IEEE 802.11abgn ESSID:off/any\n' + \
401 ' Mode:Managed Frequency:5.26 GHz Access Point: Not-Associated\n' + \
402 ' Tx-Power=15 dBm\n' + \
403 ' Retry long limit:7 RTS thr:off Fragment thr:off\n' + \
404 ' Power Management:off\n'
405 mock_popen.return_value = mock_popen_instance
406 self.assertEqual(None, self.conn_manager.get_current_essid())
408 @mock.patch('wifiradar.connections.Popen')
409 def test_get_current_bssid(self, mock_popen):
410 """ Test get_current_bssid method. """
411 self.mock_config.get_opt.return_value = 'mock_iwconfig'
412 self.mock_config.get_network_device.return_value = 'wlan0'
414 # Test associated interface.
415 mock_popen_instance = mock.Mock()
416 mock_popen_instance.stdout.read.return_value = \
417 'wlan0 IEEE 802.11abgn ESSID:"WinterPalace"\n' + \
418 ' Mode:Managed Frequency:5.26 GHz Access Point: 00:00:00:00:00:00\n' + \
419 ' Tx-Power=15 dBm\n' + \
420 ' Retry long limit:7 RTS thr:off Fragment thr:off\n' + \
421 ' Power Management:off\n'
423 mock_popen.return_value = mock_popen_instance
424 self.assertEqual('00:00:00:00:00:00', self.conn_manager.get_current_bssid())
426 # Test not associated interface.
427 mock_popen_instance = mock.Mock()
428 mock_popen_instance.stdout.read.return_value = \
429 'wlan0 IEEE 802.11abgn ESSID:off/any\n' + \
430 ' Mode:Managed Frequency:5.26 GHz Access Point: Not-Associated\n' + \
431 ' Tx-Power=15 dBm\n' + \
432 ' Retry long limit:7 RTS thr:off Fragment thr:off\n' + \
433 ' Power Management:off\n'
434 mock_popen.return_value = mock_popen_instance
435 self.assertEqual(None, self.conn_manager.get_current_bssid())