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" )
@@ -233,41 +235,49 @@ def setUp(self):
233
235
super ().setUp ()
234
236
self .admin = self ._get_admin ()
235
237
236
- @mock .patch .object (app_settings , "WHOIS_CONFIGURED" , True )
237
- @mock .patch ("openwisp_controller.config.whois.tasks.fetch_whois_details.delay" )
238
- def test_whois_task_called (self , mocked_task ):
238
+ @staticmethod
239
+ def _mocked_client_response ():
240
+ mock_response = mock .MagicMock ()
241
+ mock_response .city .name = "Mountain View"
242
+ mock_response .country .name = "United States"
243
+ mock_response .continent .name = "North America"
244
+ mock_response .postal .code = "94043"
245
+ mock_response .traits .autonomous_system_organization = "Google LLC"
246
+ mock_response .traits .autonomous_system_number = 15169
247
+ mock_response .traits .network = "172.217.22.0/24"
248
+ mock_response .location .time_zone = "America/Los_Angeles"
249
+ mock_response .location .latitude = 2
250
+ mock_response .location .longitude = 23
251
+ return mock_response
252
+
253
+ def _task_called (self , mocked_task , task_name = "WHOIS lookup" ):
239
254
org = self ._get_org ()
240
255
connect_whois_handlers ()
241
256
242
- with self .subTest (" task called when last_ip is public" ):
257
+ with self .subTest (f" { task_name } task called when last_ip is public" ):
243
258
with mock .patch ("django.core.cache.cache.set" ) as mocked_set :
244
259
device = self ._create_device (last_ip = "172.217.22.14" )
245
260
mocked_task .assert_called ()
246
261
mocked_set .assert_called_once ()
247
262
mocked_task .reset_mock ()
248
263
249
- with self .subTest ("task called when last_ip is changed and is public" ):
264
+ with self .subTest (
265
+ f"{ task_name } task called when last_ip is changed and is public"
266
+ ):
250
267
with mock .patch ("django.core.cache.cache.get" ) as mocked_get :
251
268
device .last_ip = "172.217.22.10"
252
269
device .save ()
253
270
mocked_task .assert_called ()
254
271
mocked_get .assert_called_once ()
255
272
mocked_task .reset_mock ()
256
273
257
- with self .subTest (" task not called when last_ip is private" ):
274
+ with self .subTest (f" { task_name } task not called when last_ip is private" ):
258
275
device .last_ip = "10.0.0.1"
259
276
device .save ()
260
277
mocked_task .assert_not_called ()
261
278
mocked_task .reset_mock ()
262
279
263
- with self .subTest ("task not called when last_ip has related WHOISInfo" ):
264
- device .last_ip = "172.217.22.10"
265
- self ._create_whois_info (ip_address = device .last_ip )
266
- device .save ()
267
- mocked_task .assert_not_called ()
268
- mocked_task .reset_mock ()
269
-
270
- with self .subTest ("task not called when WHOIS is disabled" ):
280
+ with self .subTest (f"{ task_name } task not called when WHOIS is disabled" ):
271
281
Device .objects .all ().delete ()
272
282
org .config_settings .whois_enabled = False
273
283
# Invalidates old org config settings cache
@@ -276,7 +286,9 @@ def test_whois_task_called(self, mocked_task):
276
286
mocked_task .assert_not_called ()
277
287
mocked_task .reset_mock ()
278
288
279
- with self .subTest ("task called via DeviceChecksumView when WHOIS is enabled" ):
289
+ with self .subTest (
290
+ f"{ task_name } task called via DeviceChecksumView when WHOIS is enabled"
291
+ ):
280
292
org .config_settings .whois_enabled = True
281
293
# Invalidates old org config settings cache
282
294
org .config_settings .save (update_fields = ["whois_enabled" ])
@@ -294,7 +306,7 @@ def test_whois_task_called(self, mocked_task):
294
306
mocked_task .reset_mock ()
295
307
296
308
with self .subTest (
297
- " task called via DeviceChecksumView when a device has no WHOIS record"
309
+ f" { task_name } task called via DeviceChecksumView when a device has no WHOIS record"
298
310
):
299
311
WHOISInfo .objects .all ().delete ()
300
312
response = self .client .get (
@@ -306,6 +318,40 @@ def test_whois_task_called(self, mocked_task):
306
318
mocked_task .assert_called ()
307
319
mocked_task .reset_mock ()
308
320
321
+ @mock .patch .object (app_settings , "WHOIS_CONFIGURED" , True )
322
+ @mock .patch ("openwisp_controller.config.whois.tasks.fetch_whois_details.delay" )
323
+ def test_whois_task_called (self , mocked_lookup_task ):
324
+ self ._task_called (mocked_lookup_task )
325
+
326
+ Device .objects .all ().delete () # Clear existing devices
327
+ device = self ._create_device ()
328
+ with self .subTest ("WHOIS lookup task not called when last_ip has related WhoIsInfo" ):
329
+ device .last_ip = "172.217.22.14"
330
+ self ._create_whois_info (ip_address = device .last_ip )
331
+ device .save ()
332
+ mocked_lookup_task .assert_not_called ()
333
+ mocked_lookup_task .reset_mock ()
334
+
335
+ @mock .patch .object (app_settings , "WHOIS_CONFIGURED" , True )
336
+ @mock .patch ("openwisp_controller.config.whois.tasks.manage_fuzzy_locations.delay" )
337
+ @mock .patch (_WHOIS_GEOIP_CLIENT )
338
+ def test_fuzzy_location_task_called (
339
+ self , mocked_client , mocked_fuzzy_location_task
340
+ ):
341
+ mocked_client .return_value .city .return_value = self ._mocked_client_response ()
342
+ self ._task_called (mocked_fuzzy_location_task , task_name = "Fuzzy location" )
343
+
344
+ Device .objects .all ().delete ()
345
+ device = self ._create_device ()
346
+ with self .subTest (
347
+ "Fuzzy location task called when last_ip has related WhoIsInfo"
348
+ ):
349
+ device .last_ip = "172.217.22.14"
350
+ self ._create_whois_info (ip_address = device .last_ip )
351
+ device .save ()
352
+ mocked_fuzzy_location_task .assert_called ()
353
+ mocked_fuzzy_location_task .reset_mock ()
354
+
309
355
@mock .patch .object (app_settings , "WHOIS_CONFIGURED" , True )
310
356
@mock .patch ("openwisp_controller.config.whois.tasks.fetch_whois_details.delay" )
311
357
def test_whois_multiple_orgs (self , mocked_task ):
@@ -410,16 +456,7 @@ def _verify_whois_details(instance, ip_address):
410
456
)
411
457
412
458
# mocking the response from the geoip2 client
413
- mock_response = mock .MagicMock ()
414
- mock_response .city .name = "Mountain View"
415
- mock_response .country .name = "United States"
416
- mock_response .continent .name = "North America"
417
- mock_response .postal .code = "94043"
418
- mock_response .traits .autonomous_system_organization = "Google LLC"
419
- mock_response .traits .autonomous_system_number = 15169
420
- mock_response .traits .network = "172.217.22.0/24"
421
- mock_response .location .time_zone = "America/Los_Angeles"
422
- mock_client .return_value .city .return_value = mock_response
459
+ mock_client .return_value .city .return_value = self ._mocked_client_response ()
423
460
424
461
with self .subTest ("Test WHOIS create when device is created" ):
425
462
device = self ._create_device (last_ip = "172.217.22.14" )
@@ -459,6 +496,109 @@ def _verify_whois_details(instance, ip_address):
459
496
# WHOIS related to the device's last_ip should be deleted
460
497
self .assertEqual (WHOISInfo .objects .filter (ip_address = ip_address ).count (), 0 )
461
498
499
+ @mock .patch .object (app_settings , "WHOIS_CONFIGURED" , True )
500
+ @mock .patch (_WHOIS_GEOIP_CLIENT )
501
+ def test_fuzzy_location_creation_and_update (self , mock_client ):
502
+ connect_whois_handlers ()
503
+
504
+ def _verify_location_details (device , mocked_response ):
505
+ location = device .devicelocation .location
506
+ mocked_location = mocked_response .location
507
+ formatted_address = ", " .join (
508
+ [
509
+ mocked_response .city .name ,
510
+ mocked_response .country .name ,
511
+ mocked_response .continent .name ,
512
+ mocked_response .postal .code ,
513
+ ]
514
+ )
515
+ self .assertEqual (location .address , formatted_address )
516
+ self .assertEqual (
517
+ location .geometry ,
518
+ GEOSGeometry (
519
+ f"POINT({ mocked_location .longitude } { mocked_location .latitude } )" ,
520
+ srid = 4326 ,
521
+ ),
522
+ )
523
+
524
+ mocked_response = self ._mocked_client_response ()
525
+ mock_client .return_value .city .return_value = mocked_response
526
+
527
+ with self .subTest ("Test Fuzzy location created when device is created" ):
528
+ device = self ._create_device (last_ip = "172.217.22.14" )
529
+
530
+ location = device .devicelocation .location
531
+ self .assertEqual (location .fuzzy , True )
532
+ self .assertEqual (location .is_mobile , False )
533
+ self .assertEqual (location .type , "outdoor" )
534
+ _verify_location_details (device , mocked_response )
535
+
536
+ with self .subTest ("Test Fuzzy location updated when last ip is updated" ):
537
+ device .last_ip = "172.217.22.10"
538
+ mocked_response .location .latitude = 100
539
+ mocked_response .location .longitude = 200
540
+ mocked_response .city .name = "New City"
541
+ mock_client .return_value .city .return_value = mocked_response
542
+ device .save ()
543
+ device .refresh_from_db ()
544
+
545
+ location = device .devicelocation .location
546
+ self .assertEqual (location .fuzzy , True )
547
+ self .assertEqual (location .is_mobile , False )
548
+ self .assertEqual (location .type , "outdoor" )
549
+ _verify_location_details (device , mocked_response )
550
+
551
+ with self .subTest (
552
+ "Test Location not updated if it is not fuzzy when last ip is updated"
553
+ ):
554
+ device .last_ip = "172.217.22.11"
555
+ device .devicelocation .location .fuzzy = False
556
+ mock_client .return_value .city .return_value = self ._mocked_client_response ()
557
+ device .devicelocation .location .save ()
558
+ device .save ()
559
+ device .refresh_from_db ()
560
+
561
+ location = device .devicelocation .location
562
+ self .assertEqual (location .fuzzy , False )
563
+ self .assertEqual (location .is_mobile , False )
564
+ self .assertEqual (location .type , "outdoor" )
565
+ _verify_location_details (device , mocked_response )
566
+
567
+ with self .subTest (
568
+ "Test Location shared among devices with same last_ip when new device's location does not exist"
569
+ ):
570
+ Device .objects .all ().delete ()
571
+ device1 = self ._create_device (last_ip = "172.217.22.10" )
572
+ device2 = self ._create_device (
573
+ name = "11:22:33:44:55:66" ,
574
+ mac_address = "11:22:33:44:55:66" ,
575
+ last_ip = "172.217.22.10" ,
576
+ )
577
+
578
+ self .assertEqual (
579
+ device1 .devicelocation .location .pk , device2 .devicelocation .location .pk
580
+ )
581
+
582
+ with self .subTest (
583
+ "Test Location shared among devices with same last_ip when new device's location exist"
584
+ ):
585
+ Device .objects .all ().delete ()
586
+ device1 = self ._create_device (last_ip = "172.217.22.10" )
587
+ device2 = self ._create_device (
588
+ name = "11:22:33:44:55:66" ,
589
+ mac_address = "11:22:33:44:55:66" ,
590
+ last_ip = "172.217.22.11" ,
591
+ )
592
+ old_location = device2 .devicelocation .location
593
+ device2 .last_ip = "172.217.22.10"
594
+ device2 .save ()
595
+ device2 .refresh_from_db ()
596
+
597
+ self .assertEqual (
598
+ device1 .devicelocation .location .pk , device2 .devicelocation .location .pk
599
+ )
600
+ self .assertEqual (Location .objects .filter (pk = old_location .pk ).count (), 0 )
601
+
462
602
# we need to allow the task to propagate exceptions to ensure
463
603
# `on_failure` method is called and notifications are executed
464
604
@override_settings (CELERY_TASK_EAGER_PROPAGATES = False )
0 commit comments