"""Unit tests for the TransferWise handlers.""" from datetime import datetime, timezone import json from unittest.mock import patch from oto import response, status import pytest from collaborator.constants import transferwise_api from collaborator.handlers import transferwise from collaborator.utils import handlers @pytest.fixture(autouse=True) def _mock_auth(mocker, mock_account): """Mock authorization for every test.""" mocker.patch.object(handlers, "_verify_profile_headers") mock_vendors_auth = mocker.patch.object(transferwise, "_account_from_vendors_auth") mock_collabs_auth = mocker.patch.object(transferwise, "_account_from_collabs_auth") mock_vendors_auth.return_value = mock_account mock_collabs_auth.return_value = mock_account @pytest.mark.parametrize( "profile_id, current_state", [ (12345, "verified"), (12345, "not_veri"), (24601, "verified"), ], ) @patch("collaborator.handlers.transferwise.crypto") @patch("collaborator.handlers.transferwise.transferwise") @patch("collaborator.handlers.transferwise.g") def test_webhook_profile_verification( g_mock, transferwise_mock, crypto_mock, test_client, profile_id, current_state ): """Test TransferWise webhook for profile verification.""" mock_response = response.Response(status=status.OK) transferwise_mock.update_verification_status.return_value = mock_response crypto_mock.verify_signature.return_value = True data = json.dumps( { "event_type": "profiles#verification-state-change", "data": { "resource": { "type": "profile", "id": profile_id, }, "current_state": current_state, }, } ) result = test_client.post( "/transferwise/webhook", data=data, content_type="application/json", headers={transferwise_api.WEBHOOK_SIGNATURE_HEADER: "test"}, ) assert result.status_code == mock_response.status transferwise_mock.update_verification_status.assert_called_with( profile_id, current_state ) assert g_mock.ows.log.warning.call_count == 4 g_mock.ows.log.warning.assert_any_call( f"WEBHOOK TW_PROFILE: {profile_id} {current_state}" ) @pytest.mark.parametrize( "transfer_id, current_state", [ (12345, "processing"), (12345, "cancelleds"), (24601, "processing"), ], ) @patch("collaborator.handlers.transferwise.crypto") @patch("collaborator.handlers.transferwise.transferwise") @patch("collaborator.handlers.transferwise.g") def test_webhook_transfer_state( g_mock, transferwise_mock, crypto_mock, test_client, transfer_id, current_state ): """Test TransferWise webhook for transfer state.""" mock_response = response.Response(status=status.OK) transferwise_mock.update_payment_status.return_value = mock_response crypto_mock.verify_signature.return_value = True data = json.dumps( { "event_type": "transfers#state-change", "data": { "resource": { "type": "transfer", "id": transfer_id, }, "current_state": current_state, "occurred_at": "1992-02-01T12:00:00.000Z", }, } ) result = test_client.post( "/transferwise/webhook", data=data, content_type="application/json", headers={transferwise_api.WEBHOOK_SIGNATURE_HEADER: "test"}, ) assert result.status_code == mock_response.status transferwise_mock.update_payment_status.assert_called_with( transfer_id, current_state, datetime(1992, 2, 1, 12, 0, tzinfo=timezone.utc) ) assert g_mock.ows.log.warning.call_count == 4 g_mock.ows.log.warning.assert_any_call( f"WEBHOOK TW_TRANSFER_STATE: {transfer_id} {current_state}" ) @patch("collaborator.handlers.transferwise.crypto") @patch("collaborator.handlers.transferwise.transferwise") def test_webhook_unhandled_event(transferwise_mock, crypto_mock, test_client): """Test /transferwise/webhook with an unhandled event type.""" crypto_mock.verify_signature.return_value = True body = {"event_type": "something wrong", "data": {"test": "lol"}} result = test_client.post( "/transferwise/webhook", data=json.dumps(body), content_type="application/json", headers=[ (transferwise_api.WEBHOOK_SIGNATURE_HEADER, "test"), ], ) assert result.status_code == status.INTERNAL_ERROR transferwise_mock.update_verification_status.assert_not_called() @patch("collaborator.handlers.transferwise.crypto") @patch("collaborator.handlers.transferwise.transferwise") @patch("collaborator.handlers.transferwise.g") def test_webhook_test_header(g_mock, transferwise_mock, crypto_mock, test_client): """Test /transferwise/webhook with the test header present.""" crypto_mock.verify_signature.return_value = True result = test_client.post( "/transferwise/webhook", headers=[ (transferwise_api.WEBHOOK_TEST_HEADER, True), (transferwise_api.WEBHOOK_SIGNATURE_HEADER, "test"), ], ) assert result.status_code == status.OK transferwise_mock.update_verification_status.assert_not_called() g_mock.ows.log.warning.assert_any_call("WEBHOOK Test header found") @pytest.mark.parametrize( "data, event_type", [ (None, None), ({"test": "lol"}, None), (None, "example"), ], ) @patch("collaborator.handlers.transferwise.crypto") @patch("collaborator.handlers.transferwise.transferwise") def test_webhook_missing_params( transferwise_mock, crypto_mock, test_client, data, event_type ): """Test /transferwise/webhook with missing parameters.""" crypto_mock.verify_signature.return_value = True body = {} if data: body["data"] = data if event_type: body["event_type"] = event_type result = test_client.post( "/transferwise/webhook", data=json.dumps(body), content_type="application/json", headers=[ (transferwise_api.WEBHOOK_SIGNATURE_HEADER, "test"), ], ) assert result.status_code == status.INTERNAL_ERROR transferwise_mock.update_verification_status.assert_not_called() @patch("collaborator.handlers.transferwise.crypto") @patch("collaborator.handlers.transferwise.transferwise") def test_webhook_missing_signature_header(transferwise_mock, crypto_mock, test_client): """Test /transferwise/webhook with missing signature header.""" result = test_client.post( "/transferwise/webhook", data=json.dumps({}), content_type="application/json", headers=[], ) assert result.status_code == status.INTERNAL_ERROR crypto_mock.verify_signature.assert_not_called() transferwise_mock.update_verification_status.assert_not_called() @patch("collaborator.handlers.transferwise.crypto") @patch("collaborator.handlers.transferwise.transferwise") def test_webhook_missing_signature_not_valid( transferwise_mock, crypto_mock, test_client ): """Test /transferwise/webhook with invalid signature header.""" crypto_mock.verify_signature.return_value = False result = test_client.post( "/transferwise/webhook", data=json.dumps({}), content_type="application/json", headers=[ (transferwise_api.WEBHOOK_SIGNATURE_HEADER, "test"), ], ) assert result.status_code == status.INTERNAL_ERROR crypto_mock.verify_signature.assert_called() transferwise_mock.update_verification_status.assert_not_called() @patch("collaborator.handlers.transferwise.transferwise") def test_get_batch(transferwise_mock, mock_account, test_client): """Test getting a batch.""" batch_id = 123 example_batch = {"id": 123, "vendor_id": 1234, "batch_id": "abcd"} transferwise_mock.check_can_access_batch.return_value = mock_account.id transferwise_mock.check_can_access_batch_group.return_value = mock_account.id transferwise_mock.get_batch.return_value = response.Response(example_batch) result = test_client.get(f"/transferwise/batch/{batch_id}") response_data = json.loads(result.data.decode()) assert result.status_code == status.OK assert response_data == example_batch transferwise_mock.get_batch.assert_called_with(int(mock_account.id), batch_id) @patch("collaborator.handlers.transferwise.transferwise") def test_get_batches(transferwise_mock, mock_account, test_client): """Test getting a batch.""" example_batch = {"id": 123, "vendor_id": 1234, "batch_id": "abcd"} transferwise_mock.check_can_access_batch.return_value = mock_account.id transferwise_mock.check_can_access_batch_group.return_value = mock_account.id transferwise_mock.get_batches.return_value = response.Response([example_batch]) result = test_client.get("/transferwise/batches") response_data = json.loads(result.data.decode()) assert result.status_code == status.OK assert response_data == [example_batch] transferwise_mock.get_batches.assert_called_with(mock_account) @patch("collaborator.handlers.transferwise.transferwise") def test_get_transactions_for_batch(transferwise_mock, mock_account, test_client): """Test getting transactions for a batch.""" batch_id = 123 example_transactions = [ {"id": 1, "fee": 1000, "collaborator_id": 3}, {"id": 2, "fee": 0, "collaborator_id": 2}, {"id": 3, "fee": 9001, "collaborator_id": 1}, ] transferwise_mock.check_can_access_batch.return_value = mock_account.id transferwise_mock.check_can_access_batch_group.return_value = mock_account.id transferwise_mock.get_transactions_for_batch.return_value = response.Response( example_transactions ) result = test_client.get(f"/transferwise/batch/{batch_id}/transactions") response_data = json.loads(result.data.decode()) assert result.status_code == status.OK assert response_data == example_transactions transferwise_mock.get_transactions_for_batch.assert_called_with( batch_id, mock_account ) @patch("collaborator.handlers.transferwise.transferwise") def test_get_transfer_requirements(transferwise_mock, mock_account, auth_client): """Test fetching transfer requirements.""" transferwise_mock.get_transfer_requirements.return_value = response.Response( status=status.NO_CONTENT ) result = auth_client.post( "/transferwise/transfer-requirements", data=json.dumps({"transfers": [{"currency": "USD", "transfer": {}}]}), content_type="application/json", ) assert result.status_code == status.NO_CONTENT transferwise_mock.get_transfer_requirements.assert_called_with( [{"currency": "USD", "transfer": {}}] )