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
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.
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')
66 test_thread
= Thread(None, wifiradar
.connections
.scanner
, args
=(mock
.Mock(), b
))
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))
72 self
.assertEqual('ACCESSPOINT', msg
.topic
)
73 self
.assertEqual(profile
, msg
.details
)
74 a
.send(Message('EXIT', ''))
78 class TestConnectionManager(unittest
.TestCase
):
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'
95 essid
= 'WinterPalace'
96 bssid
= '01:02:03:04:05:06'
98 'con_script': 'mysterious_bash_command',
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
))
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'],
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'
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'])
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. """
157 kill_args_list
= ['-k', '-d']
158 self
.mock_config
.get_opt
.side_effect
= ['mock_pidfile', kill_args
,
160 mock_access
.return_value
= True
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']
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. """
180 dhcp_args
= '-t %(timeout)s -w'
181 dhcp_args_list
= ['-t', '%(timeout)s', '-w']
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. """
204 self
.mock_config
.get_opt
.side_effect
= ['mock_pidfile', kill_cmd
,
206 mock_access
.return_value
= True
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. """
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'
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'
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
,
253 mock_shellcmd
.assert_any_call([route
, 'add', 'default',
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. """
264 'essid': 'WinterPalace',
265 'bssid': '01:02:03:04:05:06',
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',
284 self
.conn_manager
._run
_script
.assert_any_call('con_postscript',
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'
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',
314 self
.conn_manager
._run
_script
.assert_any_call('dis_postscript',
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')
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())