1
1
import importlib
2
2
from unittest import mock
3
3
4
+ from django .contrib .gis .geos import GEOSGeometry
4
5
from django .core .exceptions import ImproperlyConfigured , ValidationError
5
6
from django .db .models .signals import post_delete , post_save
6
7
from django .test import TestCase , TransactionTestCase , override_settings
14
15
from .utils import CreateWhoIsMixin
15
16
16
17
Device = load_model ("config" , "Device" )
18
+ Location = load_model ("geo" , "Location" )
17
19
WhoIsInfo = load_model ("config" , "WhoIsInfo" )
18
20
OrganizationConfigSettings = load_model ("config" , "OrganizationConfigSettings" )
19
21
Notification = load_model ("openwisp_notifications" , "Notification" )
@@ -192,11 +194,22 @@ class TestWhoIsTransaction(CreateWhoIsMixin, TransactionTestCase):
192
194
def setUp (self ):
193
195
self .admin = self ._get_admin ()
194
196
195
- @mock .patch .object (app_settings , "WHOIS_CONFIGURED" , True )
196
- @mock .patch (
197
- "openwisp_controller.config.whois.service.WhoIsService.fetch_whois_details.delay" # noqa: E501
198
- )
199
- def test_task_called (self , mocked_task ):
197
+ @staticmethod
198
+ def _mocked_client_response ():
199
+ mock_response = mock .MagicMock ()
200
+ mock_response .city .name = "Mountain View"
201
+ mock_response .country .name = "United States"
202
+ mock_response .continent .name = "North America"
203
+ mock_response .postal .code = "94043"
204
+ mock_response .traits .autonomous_system_organization = "Google LLC"
205
+ mock_response .traits .autonomous_system_number = 15169
206
+ mock_response .traits .network = "172.217.22.0/24"
207
+ mock_response .location .time_zone = "America/Los_Angeles"
208
+ mock_response .location .latitude = 2
209
+ mock_response .location .longitude = 23
210
+ return mock_response
211
+
212
+ def _task_called (self , mocked_task ):
200
213
org = self ._get_org ()
201
214
OrganizationConfigSettings .objects .create (organization = org , whois_enabled = True )
202
215
connect_whois_handlers ()
@@ -218,13 +231,6 @@ def test_task_called(self, mocked_task):
218
231
mocked_task .assert_not_called ()
219
232
mocked_task .reset_mock ()
220
233
221
- with self .subTest ("task not called when last_ip has related WhoIsInfo" ):
222
- device .last_ip = "172.217.22.10"
223
- self ._create_whois_info (ip_address = device .last_ip )
224
- device .save ()
225
- mocked_task .assert_not_called ()
226
- mocked_task .reset_mock ()
227
-
228
234
with self .subTest ("task not called when WHOIS is disabled" ):
229
235
Device .objects .all ().delete () # Clear existing devices
230
236
org .config_settings .whois_enabled = False
@@ -250,11 +256,52 @@ def test_task_called(self, mocked_task):
250
256
mocked_task .assert_called ()
251
257
mocked_task .reset_mock ()
252
258
259
+ @mock .patch .object (app_settings , "WHOIS_CONFIGURED" , True )
260
+ @mock .patch (
261
+ "openwisp_controller.config.whois.service.WhoIsService.fetch_whois_details.delay" # noqa: E501
262
+ )
263
+ def test_lookup_task_called (self , mocked_lookup_task ):
264
+ self ._task_called (mocked_lookup_task )
265
+
266
+ Device .objects .all ().delete () # Clear existing devices
267
+ device = self ._create_device ()
268
+ with self .subTest ("Lookup task not called when last_ip has related WhoIsInfo" ):
269
+ device .last_ip = "172.217.22.14"
270
+ self ._create_whois_info (ip_address = device .last_ip )
271
+ device .save ()
272
+ mocked_lookup_task .assert_not_called ()
273
+ mocked_lookup_task .reset_mock ()
274
+
275
+ @mock .patch .object (app_settings , "WHOIS_CONFIGURED" , True )
276
+ @mock .patch (
277
+ "openwisp_controller.config.whois.service.WhoIsService.manage_fuzzy_locations.delay" # noqa: E501
278
+ )
279
+ @mock .patch (_WHOIS_GEOIP_CLIENT )
280
+ def test_fuzzy_location_task_called (
281
+ self , mocked_client , mocked_fuzzy_location_task
282
+ ):
283
+ """
284
+ Test that the fuzzy location task is called alongside the lookup task
285
+ """
286
+ mocked_client .return_value .city .return_value = self ._mocked_client_response ()
287
+ self ._task_called (mocked_fuzzy_location_task )
288
+
289
+ Device .objects .all ().delete () # Clear existing devices
290
+ device = self ._create_device ()
291
+ with self .subTest (
292
+ "Fuzzy location task called when last_ip has related WhoIsInfo"
293
+ ):
294
+ device .last_ip = "172.217.22.14"
295
+ self ._create_whois_info (ip_address = device .last_ip )
296
+ device .save ()
297
+ mocked_fuzzy_location_task .assert_called ()
298
+ mocked_fuzzy_location_task .reset_mock ()
299
+
253
300
# mocking the geoip2 client to return a mock response
254
301
@mock .patch .object (app_settings , "WHOIS_CONFIGURED" , True )
255
302
@mock .patch (_WHOIS_TASKS_INFO_LOGGER )
256
303
@mock .patch (_WHOIS_GEOIP_CLIENT )
257
- def test_whois_info_tasks (self , mock_client , mock_info ):
304
+ def test_whois_info_creation (self , mock_client , mock_info ):
258
305
259
306
# helper function for asserting the model details with
260
307
# mocked api response
@@ -282,16 +329,7 @@ def _verify_whois_details(instance, ip_address):
282
329
OrganizationConfigSettings .objects .create (organization = org , whois_enabled = True )
283
330
284
331
# mocking the response from the geoip2 client
285
- mock_response = mock .MagicMock ()
286
- mock_response .city .name = "Mountain View"
287
- mock_response .country .name = "United States"
288
- mock_response .continent .name = "North America"
289
- mock_response .postal .code = "94043"
290
- mock_response .traits .autonomous_system_organization = "Google LLC"
291
- mock_response .traits .autonomous_system_number = 15169
292
- mock_response .traits .network = "172.217.22.0/24"
293
- mock_response .location .time_zone = "America/Los_Angeles"
294
- mock_client .return_value .city .return_value = mock_response
332
+ mock_client .return_value .city .return_value = self ._mocked_client_response ()
295
333
296
334
# creating a device with a last public IP
297
335
with self .subTest ("Test WHOIS create when device is created" ):
@@ -332,6 +370,112 @@ def _verify_whois_details(instance, ip_address):
332
370
# WHOIS related to the device's last_ip should be deleted
333
371
self .assertEqual (WhoIsInfo .objects .filter (ip_address = ip_address ).count (), 0 )
334
372
373
+ @mock .patch .object (app_settings , "WHOIS_CONFIGURED" , True )
374
+ @mock .patch (_WHOIS_GEOIP_CLIENT )
375
+ def test_fuzzy_location_creation_and_update (self , mock_client ):
376
+ connect_whois_handlers ()
377
+
378
+ def _verify_location_details (device , mocked_response ):
379
+ location = device .devicelocation .location
380
+ mocked_location = mocked_response .location
381
+ formatted_address = ", " .join (
382
+ [
383
+ mocked_response .city .name ,
384
+ mocked_response .country .name ,
385
+ mocked_response .continent .name ,
386
+ mocked_response .postal .code ,
387
+ ]
388
+ )
389
+ self .assertEqual (location .address , formatted_address )
390
+ self .assertEqual (
391
+ location .geometry ,
392
+ GEOSGeometry (
393
+ f"POINT({ mocked_location .longitude } { mocked_location .latitude } )" ,
394
+ srid = 4326 ,
395
+ ),
396
+ )
397
+
398
+ org = self ._get_org ()
399
+ OrganizationConfigSettings .objects .create (organization = org , whois_enabled = True )
400
+
401
+ mocked_response = self ._mocked_client_response ()
402
+ mock_client .return_value .city .return_value = mocked_response
403
+
404
+ with self .subTest ("Test Fuzzy location created when device is created" ):
405
+ device = self ._create_device (last_ip = "172.217.22.14" )
406
+
407
+ location = device .devicelocation .location
408
+ self .assertEqual (location .fuzzy , True )
409
+ self .assertEqual (location .is_mobile , False )
410
+ self .assertEqual (location .type , "outdoor" )
411
+ _verify_location_details (device , mocked_response )
412
+
413
+ with self .subTest ("Test Fuzzy location updated when last ip is updated" ):
414
+ device .last_ip = "172.217.22.10"
415
+ mocked_response .location .latitude = 100
416
+ mocked_response .location .longitude = 200
417
+ mocked_response .city .name = "New City"
418
+ mock_client .return_value .city .return_value = mocked_response
419
+ device .save ()
420
+ device .refresh_from_db ()
421
+
422
+ location = device .devicelocation .location
423
+ self .assertEqual (location .fuzzy , True )
424
+ self .assertEqual (location .is_mobile , False )
425
+ self .assertEqual (location .type , "outdoor" )
426
+ _verify_location_details (device , mocked_response )
427
+
428
+ with self .subTest (
429
+ "Test Location not updated if it is not fuzzy when last ip is updated"
430
+ ):
431
+ device .last_ip = "172.217.22.11"
432
+ device .devicelocation .location .fuzzy = False
433
+ mock_client .return_value .city .return_value = self ._mocked_client_response ()
434
+ device .devicelocation .location .save ()
435
+ device .save ()
436
+ device .refresh_from_db ()
437
+
438
+ location = device .devicelocation .location
439
+ self .assertEqual (location .fuzzy , False )
440
+ self .assertEqual (location .is_mobile , False )
441
+ self .assertEqual (location .type , "outdoor" )
442
+ _verify_location_details (device , mocked_response )
443
+
444
+ with self .subTest (
445
+ "Test Location shared among devices with same last_ip when new device's location does not exist"
446
+ ):
447
+ Device .objects .all ().delete ()
448
+ device1 = self ._create_device (last_ip = "172.217.22.10" )
449
+ device2 = self ._create_device (
450
+ name = "11:22:33:44:55:66" ,
451
+ mac_address = "11:22:33:44:55:66" ,
452
+ last_ip = "172.217.22.10" ,
453
+ )
454
+
455
+ self .assertEqual (
456
+ device1 .devicelocation .location .pk , device2 .devicelocation .location .pk
457
+ )
458
+
459
+ with self .subTest (
460
+ "Test Location shared among devices with same last_ip when new device's location exist"
461
+ ):
462
+ Device .objects .all ().delete ()
463
+ device1 = self ._create_device (last_ip = "172.217.22.10" )
464
+ device2 = self ._create_device (
465
+ name = "11:22:33:44:55:66" ,
466
+ mac_address = "11:22:33:44:55:66" ,
467
+ last_ip = "172.217.22.11" ,
468
+ )
469
+ old_location = device2 .devicelocation .location
470
+ device2 .last_ip = "172.217.22.10"
471
+ device2 .save ()
472
+ device2 .refresh_from_db ()
473
+
474
+ self .assertEqual (
475
+ device1 .devicelocation .location .pk , device2 .devicelocation .location .pk
476
+ )
477
+ self .assertEqual (Location .objects .filter (pk = old_location .pk ).count (), 0 )
478
+
335
479
# we need to allow the task to propagate exceptions to ensure
336
480
# `on_failure` method is called and notifications are executed
337
481
@override_settings (CELERY_TASK_EAGER_PROPAGATES = False )
0 commit comments