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 multiprocessing
import Pipe
24 from signal
import SIGTERM
25 from subprocess
import CalledProcessError
26 from threading
import Thread
31 import wifiradar
.connections
32 from wifiradar
.config
import ConfigManager
33 from wifiradar
.misc
import DeviceError
, get_new_profile
34 from wifiradar
.pubsub
import Message
37 class TestScanner(unittest
.TestCase
):
38 @mock.patch('wifiradar.connections.Popen')
39 def test_scanner(self
, mock_popen
):
40 """ Test scanner function. """
41 # Prepare the profile information to test against mock scan data.
43 profile
= get_new_profile()
44 profile
.update({'protocol': 'bg', 'bssid': '10:15:54:9E:2E:AA',
45 'encrypted': True, 'signal': '-58', 'essid': 'startingenlivening',
46 'channel': '2', 'mode': 'Master'})
47 profiles
.append(profile
)
48 profile
= get_new_profile()
49 profile
.update({'protocol': 'bg', 'bssid': 'B2:B0:03:1A:8F:6B',
50 'encrypted': False, 'signal': '-55', 'essid': 'filtersgenuinely',
51 'channel': '3', 'mode': 'Master'})
52 profiles
.append(profile
)
53 profile
= get_new_profile()
54 profile
.update({'protocol': 'bg', 'bssid': '10:A8:08:4C:18:26',
55 'encrypted': False, 'signal': '-54', 'essid': 'tractorsenslave',
56 'channel': '11', 'mode': 'Managed'})
57 profiles
.append(profile
)
59 # Read mock scan data from file.
60 mock_popen
.return_value
.stdout
= open('./test/data/mock-scan-001.txt', 'r')
64 test_thread
= Thread(None, wifiradar
.connections
.scanner
, args
=(mock
.Mock(), b
))
66 for profile
in profiles
:
67 # assert on poll() ensures a timeout if any expected msgs are not sent.
68 self
.assertTrue(a
.poll(5))
70 self
.assertEqual('ACCESSPOINT', msg
.topic
)
71 self
.assertEqual(profile
, msg
.details
)
72 a
.send(Message('EXIT', ''))
76 class TestConnectionManager(unittest
.TestCase
):
78 self
.conn_manager
= wifiradar
.connections
.ConnectionManager(mock
.Mock())
79 self
.mock_config
= mock
.Mock()
80 self
.conn_manager
.config
= self
.mock_config
82 def test_set_config(self
):
83 """ Test set_config method. """
84 self
.assertRaises(TypeError, self
.conn_manager
.set_config
, None)
85 self
.assertEqual(None, self
.conn_manager
.set_config(ConfigManager({})))
87 @mock.patch('wifiradar.connections.shellcmd')
88 def test__run_script(self
, mock_shellcmd
):
89 """ Test _run_script method. """
90 script_name
= 'con_script'
93 essid
= 'WinterPalace'
94 bssid
= '01:02:03:04:05:06'
96 'con_script': 'mysterious_bash_command',
102 self
.conn_manager
.get_current_ip
= mock
.Mock(return_value
=ip
)
103 self
.conn_manager
.get_current_essid
= mock
.Mock(return_value
=essid
)
104 self
.conn_manager
.get_current_bssid
= mock
.Mock(return_value
=bssid
)
106 self
.assertEqual(None,
107 self
.conn_manager
._run
_script
(script_name
, profile
, device
))
111 "WIFIRADAR_ESSID": essid
,
112 "WIFIRADAR_BSSID": bssid
,
113 "WIFIRADAR_PROFILE": '{}:{}'.format(essid
, bssid
),
114 "WIFIRADAR_ENCMODE": 'none',
115 "WIFIRADAR_SECMODE": '',
116 "WIFIRADAR_IF": device
}
117 mock_shellcmd
.assert_called_with(['mysterious_bash_command'],
120 @mock.patch('wifiradar.connections.shellcmd')
121 def test__prepare_nic(self
, mock_shellcmd
):
122 """ Test _prepare_nic method. """
123 self
.mock_config
.get_opt
.return_value
= 'mock_iwconfig'
124 self
.mock_config
.get_opt_as_bool
.return_value
= True
125 essid
= 'WinterPalace'
126 bssid
= '01:02:03:04:05:06'
134 self
.assertEqual(None,
135 self
.conn_manager
._prepare
_nic
(profile
, device
))
136 mock_shellcmd
.assert_called_with(['mock_iwconfig', device
,
137 'essid', "'{}'".format(essid
), 'key', 'off',
138 'mode', 'managed', 'channel', 11, 'ap', bssid
, 'commit'])
141 self
.mock_config
.get_opt
.return_value
= 'mock_iwconfig'
142 self
.mock_config
.get_opt_as_bool
.return_value
= True
143 mock_shellcmd
.side_effect
= CalledProcessError(250, 'mock_iwconfig')
144 self
.assertRaises(DeviceError
,
145 self
.conn_manager
._prepare
_nic
, profile
, device
)
147 @mock.patch('__builtin__.open', )
148 @mock.patch('os.kill')
149 @mock.patch('os.access')
150 @mock.patch('wifiradar.connections.shellcmd')
151 def test__stop_dhcp(self
, mock_shellcmd
, mock_access
, mock_kill
, mock_open
):
152 """ Test _stop_dhcp method. """
155 kill_args_list
= ['-k', '-d']
156 self
.mock_config
.get_opt
.side_effect
= ['mock_pidfile', kill_args
,
158 mock_access
.return_value
= True
161 self
.assertEqual(None, self
.conn_manager
._stop
_dhcp
(device
))
162 call_list
= [dhcp_cmd
]
163 call_list
.extend(kill_args_list
)
164 call_list
.append(device
)
165 mock_shellcmd
.assert_called_with(call_list
)
167 self
.mock_config
.get_opt
.side_effect
= ['mock_pidfile', '',
168 dhcp_cmd
, '', 'mock_pidfile']
170 mock_open
.return_value
.readline
.return_value
= PID
171 self
.assertEqual(None, self
.conn_manager
._stop
_dhcp
(device
))
172 mock_kill
.assert_called_with(PID
, SIGTERM
)
174 @mock.patch('wifiradar.connections.Popen')
175 def test__start_dhcp(self
, mock_popen
):
176 """ Test _start_dhcp method. """
178 dhcp_args
= '-t %(timeout)s -w'
179 dhcp_args_list
= ['-t', '%(timeout)s', '-w']
182 self
.mock_config
.get_opt
.side_effect
= [dhcp_cmd
, dhcp_args
]
183 self
.mock_config
.get_opt_as_int
.return_value
= dhcp_timeout
184 self
.conn_manager
.get_current_ip
= mock
.Mock(return_value
='0.0.0.0')
185 self
.assertEqual(None, self
.conn_manager
._start
_dhcp
(device
))
186 call_list
= [dhcp_cmd
]
187 call_list
.extend(dhcp_args_list
)
188 call_list
.append(device
)
189 mock_popen
.assert_called_with(call_list
, stderr
=None, stdout
=None)
190 # Test missing DHCP command.
191 self
.mock_config
.get_opt
.side_effect
= [dhcp_cmd
, dhcp_args
]
192 mock_popen
.side_effect
= OSError(2, 'No such file or directory')
193 self
.assertEqual(None, self
.conn_manager
._start
_dhcp
(device
))
195 @mock.patch('__builtin__.open', )
196 @mock.patch('os.kill')
197 @mock.patch('os.access')
198 @mock.patch('wifiradar.connections.shellcmd')
199 def test__stop_wpa(self
, mock_shellcmd
, mock_access
, mock_kill
, mock_open
):
200 """ Test _stop_wpa method. """
202 self
.mock_config
.get_opt
.side_effect
= ['mock_pidfile', kill_cmd
,
204 mock_access
.return_value
= True
207 mock_open
.return_value
.readline
.return_value
= PID
208 self
.assertEqual(None, self
.conn_manager
._stop
_wpa
())
209 mock_kill
.assert_called_with(PID
, SIGTERM
)
211 @mock.patch('wifiradar.connections.shellcmd')
212 def test__start_wpa(self
, mock_shellcmd
):
213 """ Test _start_wpa method. """
215 wpa_args
= '-B -i %(interface)s -c %(configuration)s'
216 wpa_args_list
= ['-B', '-i', '%(interface)s', '-c', '%(configuration)s']
217 self
.mock_config
.get_opt
.side_effect
= [wpa_cmd
, wpa_args
]
218 self
.assertEqual(None,
219 self
.conn_manager
._start
_wpa
())
220 call_list
= [wpa_cmd
]
221 call_list
.extend(wpa_args_list
)
222 mock_shellcmd
.assert_called_with(call_list
)
224 @mock.patch('__builtin__.open', )
225 @mock.patch('wifiradar.connections.shellcmd')
226 def test__start_manual_network(self
, mock_shellcmd
, mock_open
):
227 """ Test _start_manual_network method. """
228 ifconfig
= 'mock_ifconfig'
231 netmask
= '192.168.1.255'
232 gateway
= '192.168.1.1'
233 domain
= 'winterpalace.org'
234 dns1
= '192.168.1.200'
235 dns2
= '192.168.1.201'
244 self
.mock_config
.get_opt
.side_effect
= [ifconfig
, ifconfig
, route
]
246 self
.assertEqual(None,
247 self
.conn_manager
._start
_manual
_network
(profile
, device
))
248 mock_shellcmd
.assert_any_call([ifconfig
, device
, 'down'])
249 mock_shellcmd
.assert_any_call([ifconfig
, device
, ip
,
251 mock_shellcmd
.assert_any_call([route
, 'add', 'default',
253 # Test mock-writing the resolv file.
254 mock_open
.assert_called_with('/etc/resolv.conf', 'w')
255 # Check that resolv_file.write was called with the proper args.
256 mock_open
.return_value
.__enter
__.return_value
.write
.assert_called_with(
257 'domain {}\nnameserver {}\nnameserver {}\n'.format(domain
, dns1
, dns2
))
259 def test_connect(self
):
260 """ Test connect method. """
262 'essid': 'WinterPalace',
263 'bssid': '01:02:03:04:05:06',
267 self
.mock_config
.get_network_device
.return_value
= device
269 self
.conn_manager
._run
_script
= mock
.Mock(name
='_run_script')
270 self
.conn_manager
._prepare
_nic
= mock
.Mock(name
='_prepare_nic')
271 self
.conn_manager
._stop
_dhcp
= mock
.Mock(name
='_stop_dhcp')
272 self
.conn_manager
._start
_dhcp
= mock
.Mock(name
='_start_dhcp')
273 self
.conn_manager
._stop
_wpa
= mock
.Mock(name
='_stop_wpa')
274 self
.conn_manager
._start
_wpa
= mock
.Mock(name
='_start_wpa')
275 self
.conn_manager
._start
_manual
_network
= mock
.Mock(
276 name
='_start_manual_network')
278 # Test DHCP and WPA network configuration.
279 self
.assertEqual(None, self
.conn_manager
.connect(profile
))
280 self
.conn_manager
._run
_script
.assert_any_call('con_prescript',
282 self
.conn_manager
._run
_script
.assert_any_call('con_postscript',
284 self
.conn_manager
._prepare
_nic
.assert_called_with(profile
, device
)
285 self
.conn_manager
._stop
_dhcp
.assert_called_with(device
)
286 self
.conn_manager
._start
_dhcp
.assert_called_with(device
)
287 self
.conn_manager
._stop
_wpa
.assert_called_with()
288 self
.conn_manager
._start
_wpa
.assert_called_with()
290 # Test manual network configuration.
291 profile
['use_dhcp'] = False
292 self
.assertEqual(None, self
.conn_manager
.connect(profile
))
293 self
.conn_manager
._start
_manual
_network
.assert_called_with(profile
, device
)
295 @mock.patch('wifiradar.connections.shellcmd')
296 def test_disconnect(self
, mock_shellcmd
):
297 """ Test disconnect method. """
298 iwconfig
= 'mock_iwconfig'
299 ifconfig
= 'mock_ifconfig'
302 self
.mock_config
.get_network_device
.return_value
= device
303 self
.mock_config
.get_opt
.side_effect
= [iwconfig
, ifconfig
]
304 self
.conn_manager
._run
_script
= mock
.Mock(name
='_run_script')
305 self
.conn_manager
._stop
_dhcp
= mock
.Mock(name
='_stop_dhcp')
306 self
.conn_manager
._stop
_wpa
= mock
.Mock(name
='_stop_wpa')
307 self
.conn_manager
.if_change
= mock
.Mock(name
='if_change')
309 self
.assertEqual(None, self
.conn_manager
.disconnect(profile
))
310 self
.conn_manager
._run
_script
.assert_any_call('dis_prescript',
312 self
.conn_manager
._run
_script
.assert_any_call('dis_postscript',
314 self
.conn_manager
._stop
_dhcp
.assert_called_with(device
)
315 self
.conn_manager
._stop
_wpa
.assert_called_with()
317 mock_shellcmd
.assert_any_call([iwconfig
, device
,
318 'essid', 'any', 'key', 'off', 'mode', 'managed',
319 'channel', 'auto', 'ap', 'off'])
320 mock_shellcmd
.assert_any_call([ifconfig
, device
, '0.0.0.0'])
322 @mock.patch('wifiradar.connections.Popen')
323 def test_if_change(self
, mock_popen
):
324 """ Test if_change method. """
325 self
.mock_config
.get_opt
.return_value
= 'mock_ifconfig'
326 self
.mock_config
.get_network_device
.return_value
= 'wlan0'
328 # Test normal operation.
329 self
.assertEqual(None, self
.conn_manager
.if_change('up'))
330 self
.assertEqual(None, self
.conn_manager
.if_change('down'))
331 self
.assertEqual(None, self
.conn_manager
.if_change('UP'))
332 self
.assertEqual(None, self
.conn_manager
.if_change('DOWN'))
334 # Test unknown state.
335 self
.assertRaises(ValueError, self
.conn_manager
.if_change
, 'sideways')
337 # Test failed state change with found ifconfig.
338 mock_popen_instance
= mock
.MagicMock()
339 mock_popen_instance
.stdout
.__iter
__.return_value
= [
340 'wlan0: ERROR while getting interface flags: No such device']
341 mock_popen
.return_value
= mock_popen_instance
342 self
.assertRaises(DeviceError
, self
.conn_manager
.if_change
, 'up')
345 mock_popen
.side_effect
= OSError(2, 'No such file or directory')
346 self
.assertRaises(OSError, self
.conn_manager
.if_change
, 'up')
348 @mock.patch('wifiradar.connections.Popen')
349 def test_get_current_ip(self
, mock_popen
):
350 """ Test get_current_ip method. """
351 self
.mock_config
.get_opt
.return_value
= 'mock_ifconfig'
352 self
.mock_config
.get_network_device
.return_value
= 'wlan0'
354 # Test connected and with an IP address.
355 mock_popen_instance
= mock
.Mock()
356 mock_popen_instance
.stdout
.read
.return_value
= \
357 'wlan0: flags=4098<BROADCAST,MULTICAST> mtu 1500\n' + \
358 'inet addr:192.168.1.1 netmask 255.255.255.0 broadcast 192.168.1.255\n' + \
359 'ether 00:00:00:00:00:00 txqueuelen 1000 (Ethernet)\n' + \
360 'RX packets 246 bytes 259631 (253.5 KiB)\n' + \
361 'RX errors 0 dropped 1 overruns 0 frame 0\n' + \
362 'TX packets 123 bytes 140216 (136.9 KiB)\n' + \
363 'TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0\n'
364 mock_popen
.return_value
= mock_popen_instance
365 self
.assertEqual('192.168.1.1', self
.conn_manager
.get_current_ip())
367 # Test not connected and without an IP address.
368 mock_popen_instance
.stdout
.read
.return_value
= \
369 'wlan0: flags=4098<BROADCAST,MULTICAST> mtu 1500\n' + \
370 'ether 00:00:00:00:00:00 txqueuelen 1000 (Ethernet)\n' + \
371 'RX packets 246 bytes 259631 (253.5 KiB)\n' + \
372 'RX errors 0 dropped 1 overruns 0 frame 0\n' + \
373 'TX packets 123 bytes 140216 (136.9 KiB)\n' + \
374 'TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0\n'
375 mock_popen
.return_value
= mock_popen_instance
376 self
.assertEqual(None, self
.conn_manager
.get_current_ip())
378 @mock.patch('wifiradar.connections.Popen')
379 def test_get_current_essid(self
, mock_popen
):
380 """ Test get_current_essid method. """
381 self
.mock_config
.get_opt
.return_value
= 'mock_iwconfig'
382 self
.mock_config
.get_network_device
.return_value
= 'wlan0'
384 # Test associated interface.
385 mock_popen_instance
= mock
.Mock()
386 mock_popen_instance
.stdout
.read
.return_value
= \
387 'wlan0 IEEE 802.11abgn ESSID:"WinterPalace"\n' + \
388 ' Mode:Managed Frequency:5.26 GHz Access Point: 00:00:00:00:00:00\n' + \
389 ' Tx-Power=15 dBm\n' + \
390 ' Retry long limit:7 RTS thr:off Fragment thr:off\n' + \
391 ' Power Management:off\n'
392 mock_popen
.return_value
= mock_popen_instance
393 self
.assertEqual('WinterPalace', self
.conn_manager
.get_current_essid())
395 # Test not associated interface.
396 mock_popen_instance
= mock
.Mock()
397 mock_popen_instance
.stdout
.read
.return_value
= \
398 'wlan0 IEEE 802.11abgn ESSID:off/any\n' + \
399 ' Mode:Managed Frequency:5.26 GHz Access Point: Not-Associated\n' + \
400 ' Tx-Power=15 dBm\n' + \
401 ' Retry long limit:7 RTS thr:off Fragment thr:off\n' + \
402 ' Power Management:off\n'
403 mock_popen
.return_value
= mock_popen_instance
404 self
.assertEqual(None, self
.conn_manager
.get_current_essid())
406 @mock.patch('wifiradar.connections.Popen')
407 def test_get_current_bssid(self
, mock_popen
):
408 """ Test get_current_bssid method. """
409 self
.mock_config
.get_opt
.return_value
= 'mock_iwconfig'
410 self
.mock_config
.get_network_device
.return_value
= 'wlan0'
412 # Test associated interface.
413 mock_popen_instance
= mock
.Mock()
414 mock_popen_instance
.stdout
.read
.return_value
= \
415 'wlan0 IEEE 802.11abgn ESSID:"WinterPalace"\n' + \
416 ' Mode:Managed Frequency:5.26 GHz Access Point: 00:00:00:00:00:00\n' + \
417 ' Tx-Power=15 dBm\n' + \
418 ' Retry long limit:7 RTS thr:off Fragment thr:off\n' + \
419 ' Power Management:off\n'
421 mock_popen
.return_value
= mock_popen_instance
422 self
.assertEqual('00:00:00:00:00:00', self
.conn_manager
.get_current_bssid())
424 # Test not associated interface.
425 mock_popen_instance
= mock
.Mock()
426 mock_popen_instance
.stdout
.read
.return_value
= \
427 'wlan0 IEEE 802.11abgn ESSID:off/any\n' + \
428 ' Mode:Managed Frequency:5.26 GHz Access Point: Not-Associated\n' + \
429 ' Tx-Power=15 dBm\n' + \
430 ' Retry long limit:7 RTS thr:off Fragment thr:off\n' + \
431 ' Power Management:off\n'
432 mock_popen
.return_value
= mock_popen_instance
433 self
.assertEqual(None, self
.conn_manager
.get_current_bssid())