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