def test_get_devices_for_safe_and_owners(self): safe_contract = SafeContractFactory() self.assertEqual( FirebaseDeviceOwner.objects.get_devices_for_safe_and_owners( safe_contract.address, []), []) self.assertEqual( FirebaseDeviceOwner.objects.get_devices_for_safe_and_owners( safe_contract.address, [Account.create().address]), []) firebase_device_owner = FirebaseDeviceOwnerFactory.create( firebase_device__safes=(safe_contract, )) firebase_device_owner_2 = FirebaseDeviceOwnerFactory( firebase_device=firebase_device_owner.firebase_device) self.assertEqual( FirebaseDeviceOwner.objects.get_devices_for_safe_and_owners( Account.create().address, []), []) self.assertEqual( FirebaseDeviceOwner.objects.get_devices_for_safe_and_owners( safe_contract.address, [firebase_device_owner.owner, firebase_device_owner_2.owner]), [firebase_device_owner.firebase_device.cloud_messaging_token]) safe_contract_2 = SafeContractFactory() firebase_device_owner_3 = FirebaseDeviceOwnerFactory.create( firebase_device__safes=(safe_contract_2, )) self.assertEqual( FirebaseDeviceOwner.objects.get_devices_for_safe_and_owners( safe_contract.address, [ firebase_device_owner.owner, firebase_device_owner_2.owner, firebase_device_owner_3.owner ]), [firebase_device_owner.firebase_device.cloud_messaging_token]) self.assertEqual( FirebaseDeviceOwner.objects.get_devices_for_safe_and_owners( safe_contract_2.address, [ firebase_device_owner.owner, firebase_device_owner_2.owner, firebase_device_owner_3.owner ]), [firebase_device_owner_3.firebase_device.cloud_messaging_token]) firebase_device_owner_3.firebase_device.safes.add(safe_contract) self.assertCountEqual( FirebaseDeviceOwner.objects.get_devices_for_safe_and_owners( safe_contract.address, [ firebase_device_owner.owner, firebase_device_owner_2.owner, firebase_device_owner_3.owner ]), [ firebase_device_owner.firebase_device.cloud_messaging_token, firebase_device_owner_3.firebase_device.cloud_messaging_token ])
def test_send_notification_owner_task(self): from ..tasks import logger as task_logger safe_contract = SafeContractFactory() safe_address = safe_contract.address threshold = 2 owners = [Account.create().address for _ in range(2)] safe_tx_hash = Web3.keccak(text='hola').hex() with self.assertLogs(logger=task_logger) as cm: self.assertEqual(send_notification_owner_task(safe_address, safe_tx_hash), (0, 0)) self.assertIn('Cannot find threshold information', cm.output[0]) safe_status = SafeStatusFactory(address=safe_address, threshold=1, owners=owners) with self.assertLogs(logger=task_logger) as cm: self.assertEqual(send_notification_owner_task(safe_address, safe_tx_hash), (0, 0)) self.assertIn('No need to send confirmation notification for ', cm.output[0]) safe_status.threshold = threshold safe_status.save(update_fields=['threshold']) with self.assertLogs(logger=task_logger) as cm: self.assertEqual(send_notification_owner_task(safe_address, safe_tx_hash), (0, 0)) self.assertIn('No cloud messaging tokens found', cm.output[0]) firebase_device_owner_factories = [FirebaseDeviceOwnerFactory(owner=owner) for owner in owners] # Notification is not sent to both owners as they are not related to the safe address self.assertEqual(send_notification_owner_task(safe_address, safe_tx_hash), (0, 0)) for firebase_device_owner in firebase_device_owner_factories: firebase_device_owner.firebase_device.safes.add(safe_contract) self.assertEqual(send_notification_owner_task(safe_address, safe_tx_hash), (2, 0)) # Duplicated notifications are not sent with self.assertLogs(logger=task_logger) as cm: self.assertEqual(send_notification_owner_task(safe_address, safe_tx_hash), (0, 0)) self.assertIn('Duplicated notification', cm.output[0]) # Disable duplicated detection with mock.patch.object(DuplicateNotification, 'is_duplicated', autospec=True, return_value=False): self.assertEqual(send_notification_owner_task(safe_address, safe_tx_hash), (2, 0)) # Add one confirmation for that transaction and other random confirmation for other transaction # to check that they don't influence each other multisig_confirmation = MultisigConfirmationFactory(owner=owners[0], multisig_transaction__safe_tx_hash=safe_tx_hash) MultisigConfirmationFactory(owner=owners[1]) # Not related multisig transaction # Just one transaction sent, as owners[0] already confirmed self.assertEqual(send_notification_owner_task(safe_address, safe_tx_hash), (1, 0)) # Reach the threshold with an unrelated owner MultisigConfirmationFactory(multisig_transaction=multisig_confirmation.multisig_transaction) with self.assertLogs(logger=task_logger) as cm: self.assertEqual(send_notification_owner_task(safe_address, safe_tx_hash), (0, 0)) self.assertIn('does not require more confirmations', cm.output[0])
def test_notifications_devices_safe_delete_view(self): safe_contract = SafeContractFactory() firebase_device = FirebaseDeviceFactory() firebase_device_owner = FirebaseDeviceOwnerFactory( firebase_device=firebase_device) not_related_firebase_device_owner = FirebaseDeviceOwnerFactory() firebase_device.safes.add(safe_contract) device_id = firebase_device.uuid # Test not existing `safe_contract`, even if `device_id` is correct random_safe_address = Account.create().address self.assertEqual(firebase_device.safes.count(), 1) self.assertEqual(FirebaseDeviceOwner.objects.count(), 2) response = self.client.delete(reverse( 'v1:notifications-devices-safes-delete', args=(device_id, random_safe_address)), format='json') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(firebase_device.safes.count(), 1) self.assertEqual(FirebaseDeviceOwner.objects.count(), 2) # Happy path response = self.client.delete(reverse( 'v1:notifications-devices-safes-delete', args=(device_id, safe_contract.address)), format='json') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(firebase_device.safes.count(), 0) self.assertEqual(FirebaseDeviceOwner.objects.count(), 1) self.assertEqual(FirebaseDeviceOwner.objects.get(), not_related_firebase_device_owner) # Try to delete again and get the same result even if the Safe is not linked response = self.client.delete(reverse( 'v1:notifications-devices-safes-delete', args=(device_id, safe_contract.address)), format='json') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(firebase_device.safes.count(), 0) self.assertEqual(FirebaseDeviceOwner.objects.count(), 1) # Remove not existing Safe should not trigger an error response = self.client.delete(reverse( 'v1:notifications-devices-safes-delete', args=(device_id, Account.create().address)), format='json') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(firebase_device.safes.count(), 0) self.assertEqual(FirebaseDeviceOwner.objects.count(), 1)
def test_notifications_devices_delete_view(self): safe_contract = SafeContractFactory() firebase_device = FirebaseDeviceFactory() firebase_device.safes.add(safe_contract) device_id = firebase_device.uuid self.assertEqual(FirebaseDevice.objects.count(), 1) response = self.client.delete(reverse( 'v1:notifications-devices-delete', args=(device_id, )), format='json') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(FirebaseDevice.objects.count(), 0) # Try to delete again if not exists response = self.client.delete(reverse( 'v1:notifications-devices-delete', args=(device_id, )), format='json') self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_notifications_devices_create_view(self): response = self.client.post(reverse('v1:notifications-devices')) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) safe_address = Account.create().address safe_contract = SafeContractFactory(address=safe_address) self.assertEqual(FirebaseDevice.objects.count(), 0) data = { 'safes': [safe_address], 'cloudMessagingToken': 'A' * 163, 'buildNumber': 0, 'bundle': 'company.package.app', 'deviceType': 'WEB', 'version': '2.0.1', } response = self.client.post(reverse('v1:notifications-devices'), format='json', data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(FirebaseDevice.objects.count(), 1) device_uuid = response.data['uuid'] self.assertTrue(uuid.UUID(device_uuid)) # Same request should return a 400 because a new device with same push token cannot be created response = self.client.post(reverse('v1:notifications-devices'), format='json', data=data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) # Request with `uuid` should not create a new object data['uuid'] = device_uuid response = self.client.post(reverse('v1:notifications-devices'), format='json', data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(FirebaseDevice.objects.count(), 1) # Changing the token and using the uuid will change the cloud messaging token data['cloudMessagingToken'] = 'B' * 163 response = self.client.post(reverse('v1:notifications-devices'), format='json', data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(FirebaseDevice.objects.count(), 1) self.assertEqual(FirebaseDevice.objects.first().cloud_messaging_token, data['cloudMessagingToken']) # Add the same FirebaseDevice to another Safe safe_contract_2 = SafeContractFactory() data['safes'].append(safe_contract_2.address) response = self.client.post(reverse('v1:notifications-devices'), format='json', data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(safe_contract.firebase_devices.count(), 1) self.assertEqual(safe_contract_2.firebase_devices.count(), 1) self.assertEqual(FirebaseDevice.objects.count(), 1) self.assertEqual(FirebaseDevice.objects.first().safes.count(), 2) # Use not valid deviceType previous_device_type = data['deviceType'] data['deviceType'] = 'RANGER-MORPHER' response = self.client.post(reverse('v1:notifications-devices'), format='json', data=data) self.assertIn('is not a valid choice', response.content.decode()) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(safe_contract.firebase_devices.count(), 1) data['deviceType'] = previous_device_type # Use not valid version previous_version = data['version'] data['version'] = 'Megazord' response = self.client.post(reverse('v1:notifications-devices'), format='json', data=data) self.assertIn('Semantic version was expected', response.content.decode()) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(safe_contract.firebase_devices.count(), 1) data['version'] = previous_version # Remove one of the Safes data['safes'] = [safe_contract_2.address] response = self.client.post(reverse('v1:notifications-devices'), format='json', data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(safe_contract.firebase_devices.count(), 0) self.assertEqual(safe_contract_2.firebase_devices.count(), 1)
def test_notifications_devices_create_with_signatures_view(self): safe_address = Account.create().address safe_contract = SafeContractFactory(address=safe_address) owner_account = Account.create() owner_account_2 = Account.create() self.assertEqual(FirebaseDevice.objects.count(), 0) unique_id = uuid.uuid4() timestamp = int(time.time()) cloud_messaging_token = 'A' * 163 safes = [safe_address] hash_to_sign = calculate_device_registration_hash( timestamp, unique_id, cloud_messaging_token, safes) signatures = [owner_account.signHash(hash_to_sign)['signature'].hex()] data = { 'uuid': unique_id, 'safes': [safe_address], 'cloudMessagingToken': cloud_messaging_token, 'buildNumber': 0, 'bundle': 'company.package.app', 'deviceType': 'WEB', 'version': '2.0.1', 'timestamp': timestamp, 'signatures': signatures, } response = self.client.post(reverse('v1:notifications-devices'), format='json', data=data) # self.assertIn('is not an owner of any of the safes', str(response.data['non_field_errors'])) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data['owners_registered'], []) self.assertEqual(response.data['owners_not_registered'], [owner_account.address]) with mock.patch( 'safe_transaction_service.notifications.serializers.get_safe_owners', return_value=[owner_account.address]): response = self.client.post(reverse('v1:notifications-devices'), format='json', data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data['uuid'], str(unique_id)) self.assertEqual(FirebaseDevice.objects.count(), 1) self.assertEqual(FirebaseDeviceOwner.objects.count(), 1) self.assertEqual(FirebaseDeviceOwner.objects.first().owner, owner_account.address) # Add another signature signatures.append( owner_account_2.signHash(hash_to_sign)['signature'].hex()) response = self.client.post(reverse('v1:notifications-devices'), format='json', data=data) # self.assertIn('is not an owner of any of the safes', str(response.data['non_field_errors'])) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data['owners_registered'], [owner_account.address]) self.assertEqual(response.data['owners_not_registered'], [owner_account_2.address]) with mock.patch( 'safe_transaction_service.notifications.serializers.get_safe_owners', return_value=[owner_account.address, owner_account_2.address]): response = self.client.post(reverse('v1:notifications-devices'), format='json', data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data['owners_registered'], [owner_account.address, owner_account_2.address]) self.assertEqual(response.data['owners_not_registered'], []) self.assertEqual(FirebaseDevice.objects.count(), 1) self.assertCountEqual( FirebaseDeviceOwner.objects.values_list('owner', flat=True), [owner_account.address, owner_account_2.address])
def test_notifications_devices_create_view(self): response = self.client.post(reverse("v1:notifications:devices")) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) safe_address = Account.create().address safe_contract = SafeContractFactory(address=safe_address) self.assertEqual(FirebaseDevice.objects.count(), 0) data = { "safes": [safe_address], "cloudMessagingToken": "A" * 163, "buildNumber": 0, "bundle": "company.package.app", "deviceType": "WEB", "version": "2.0.1", } response = self.client.post(reverse("v1:notifications:devices"), format="json", data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(FirebaseDevice.objects.count(), 1) device_uuid = response.data["uuid"] self.assertTrue(uuid.UUID(device_uuid)) # Same request should return a 400 because a new device with same push token cannot be created response = self.client.post(reverse("v1:notifications:devices"), format="json", data=data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) # Request with `uuid` should not create a new object data["uuid"] = device_uuid response = self.client.post(reverse("v1:notifications:devices"), format="json", data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(FirebaseDevice.objects.count(), 1) # Changing the token and using the uuid will change the cloud messaging token data["cloudMessagingToken"] = "B" * 163 response = self.client.post(reverse("v1:notifications:devices"), format="json", data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(FirebaseDevice.objects.count(), 1) self.assertEqual( FirebaseDevice.objects.first().cloud_messaging_token, data["cloudMessagingToken"], ) # Add the same FirebaseDevice to another Safe safe_contract_2 = SafeContractFactory() data["safes"].append(safe_contract_2.address) response = self.client.post(reverse("v1:notifications:devices"), format="json", data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(safe_contract.firebase_devices.count(), 1) self.assertEqual(safe_contract_2.firebase_devices.count(), 1) self.assertEqual(FirebaseDevice.objects.count(), 1) self.assertEqual(FirebaseDevice.objects.first().safes.count(), 2) # Use not valid deviceType previous_device_type = data["deviceType"] data["deviceType"] = "RANGER-MORPHER" response = self.client.post(reverse("v1:notifications:devices"), format="json", data=data) self.assertIn("is not a valid choice", response.content.decode()) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(safe_contract.firebase_devices.count(), 1) data["deviceType"] = previous_device_type # Use not valid version previous_version = data["version"] data["version"] = "Megazord" response = self.client.post(reverse("v1:notifications:devices"), format="json", data=data) self.assertIn("Semantic version was expected", response.content.decode()) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(safe_contract.firebase_devices.count(), 1) data["version"] = previous_version # Remove one of the Safes data["safes"] = [safe_contract_2.address] response = self.client.post(reverse("v1:notifications:devices"), format="json", data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(safe_contract.firebase_devices.count(), 0) self.assertEqual(safe_contract_2.firebase_devices.count(), 1)
def test_notifications_devices_create_with_signatures_view(self): safe_address = Account.create().address safe_contract = SafeContractFactory(address=safe_address) owner_account = Account.create() owner_account_2 = Account.create() self.assertEqual(FirebaseDevice.objects.count(), 0) unique_id = uuid.uuid4() timestamp = int(time.time()) cloud_messaging_token = "A" * 163 safes = [safe_address] hash_to_sign = calculate_device_registration_hash( timestamp, unique_id, cloud_messaging_token, safes) signatures = [owner_account.signHash(hash_to_sign)["signature"].hex()] data = { "uuid": unique_id, "safes": [safe_address], "cloudMessagingToken": cloud_messaging_token, "buildNumber": 0, "bundle": "company.package.app", "deviceType": "WEB", "version": "2.0.1", "timestamp": timestamp, "signatures": signatures, } response = self.client.post(reverse("v1:notifications:devices"), format="json", data=data) # self.assertIn('is not an owner of any of the safes', str(response.data['non_field_errors'])) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data["owners_registered"], []) self.assertEqual(response.data["owners_not_registered"], [owner_account.address]) with mock.patch( "safe_transaction_service.notifications.serializers.get_safe_owners", return_value=[owner_account.address], ): response = self.client.post(reverse("v1:notifications:devices"), format="json", data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data["uuid"], str(unique_id)) self.assertEqual(FirebaseDevice.objects.count(), 1) self.assertEqual(FirebaseDeviceOwner.objects.count(), 1) self.assertEqual(FirebaseDeviceOwner.objects.first().owner, owner_account.address) # Add another signature signatures.append( owner_account_2.signHash(hash_to_sign)["signature"].hex()) response = self.client.post(reverse("v1:notifications:devices"), format="json", data=data) # self.assertIn('is not an owner of any of the safes', str(response.data['non_field_errors'])) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data["owners_registered"], [owner_account.address]) self.assertEqual(response.data["owners_not_registered"], [owner_account_2.address]) with mock.patch( "safe_transaction_service.notifications.serializers.get_safe_owners", return_value=[owner_account.address, owner_account_2.address], ): response = self.client.post(reverse("v1:notifications:devices"), format="json", data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual( response.data["owners_registered"], [owner_account.address, owner_account_2.address], ) self.assertEqual(response.data["owners_not_registered"], []) self.assertEqual(FirebaseDevice.objects.count(), 1) self.assertCountEqual( FirebaseDeviceOwner.objects.values_list("owner", flat=True), [owner_account.address, owner_account_2.address], )