diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 305f8bd..cdb067c 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -12,7 +12,15 @@ permissions: jobs: lint: + name: Lint uses: ./.github/workflows/lint.yaml + typecheck: + name: Type Check needs: lint - uses: ./.github/workflows/typecheck.yaml \ No newline at end of file + uses: ./.github/workflows/typecheck.yaml + + tests: + name: Tests + needs: [lint, typecheck] + uses: ./.github/workflows/tests.yaml diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..4e58210 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,35 @@ +name: Tests +on: + workflow_call: + +jobs: + test: + name: Run tests + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.12", "3.13"] + steps: + - uses: actions/checkout@v4 + + - name: Setup Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: Sync dependencies + run: uv sync --group dev --group test + + - name: Run tests + run: uv run pytest --tb=short -v + + - name: Test summary + if: always() + run: | + echo "### Test Results" >> $GITHUB_STEP_SUMMARY + echo "Python version: ${{ matrix.python-version }}" >> $GITHUB_STEP_SUMMARY + echo "Status: ${{ job.status }}" >> $GITHUB_STEP_SUMMARY + diff --git a/.github/workflows/typecheck.yaml b/.github/workflows/typecheck.yaml index 252c8cf..57aa6bc 100644 --- a/.github/workflows/typecheck.yaml +++ b/.github/workflows/typecheck.yaml @@ -17,8 +17,8 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v4 - - name: Sync dev deps - run: uv sync --group dev + - name: Sync dependencies + run: uv sync --group dev --group test - name: Type check run: uv run pyright diff --git a/Makefile b/Makefile index 55e3f5a..6cac28c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: lint-check lint-fix typecheck +.PHONY: lint-check lint-fix typecheck test lint-check: uv run ruff format --check @@ -11,3 +11,6 @@ lint-fix: typecheck: uv run pyright uv run mypy . + +test: + uv run pytest diff --git a/pyproject.toml b/pyproject.toml index c243bd4..53e58c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,11 @@ dev = [ "pyright==1.1.399", "mypy", ] +test = [ + "pytest>=7.0.0", + "pytest-asyncio>=0.21.0", + "pytest-cov>=4.0.0", +] [tool.ruff] line-length = 120 @@ -99,3 +104,11 @@ disallow_untyped_decorators = true exclude = [ "^examples/", ] + +[[tool.mypy.overrides]] +module = "tests.*" +disallow_untyped_defs = false +disallow_untyped_calls = false +disallow_untyped_decorators = false +disallow_incomplete_defs = false +warn_unreachable = false diff --git a/pyrightconfig.json b/pyrightconfig.json index edd1b3f..b91eba9 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -1,5 +1,5 @@ { - "include": ["src"], + "include": ["src", "tests"], "exclude": [".venv", ".git", "examples/**", "**/__pycache__"], "typeCheckingMode": "strict", "pythonVersion": "3.12", @@ -8,10 +8,17 @@ "root": "src/blindpay/resources", "reportArgumentType": "error", "reportTypedDictNotRequiredAccess": "error", - "reportMissingTypeArgument": "error", + "reportMissingTypeArgument": "error" }, { - "root": "src/blindpay", + "root": "src/blindpay" + }, + { + "root": "tests", + "reportPrivateUsage": "none", + "reportUnknownMemberType": "none", + "reportUnknownArgumentType": "none", + "reportUntypedFunctionDecorator": "none" } ] } \ No newline at end of file diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..cb898fb --- /dev/null +++ b/pytest.ini @@ -0,0 +1,13 @@ +[pytest] +testpaths = tests +python_files = test_*.py +python_classes = Test* +python_functions = test_* +asyncio_mode = auto +addopts = + -v + --tb=short + --strict-markers +markers = + asyncio: mark test as an asyncio test + diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..c505d39 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,5 @@ +import sys +from pathlib import Path + +src_path = Path(__file__).parent.parent / "src" +sys.path.insert(0, str(src_path)) diff --git a/tests/resources/__init__.py b/tests/resources/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/resources/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/resources/test_api_keys.py b/tests/resources/test_api_keys.py new file mode 100644 index 0000000..56865f3 --- /dev/null +++ b/tests/resources/test_api_keys.py @@ -0,0 +1,188 @@ +from unittest.mock import patch + +import pytest + +from blindpay import BlindPay, BlindPaySync + + +class TestApiKeys: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPay(api_key="test-key", instance_id="in_000000000000") + + @pytest.mark.asyncio + async def test_create_api_key(self): + mocked_api_key = { + "id": "ap_000000000000", + "token": "token", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_api_key, "error": None} + + response = await self.blindpay.instances.api_keys.create( + { + "name": "test", + "permission": "full_access", + "ip_whitelist": [], + } + ) + + assert response["error"] is None + assert response["data"] == mocked_api_key + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/api-keys", + {"name": "test", "permission": "full_access", "ip_whitelist": []}, + ) + + @pytest.mark.asyncio + async def test_get_api_key(self): + mocked_api_key = { + "id": "ap_000000000000", + "token": "token", + "name": "test", + "permission": "full_access", + "ip_whitelist": ["127.0.0.1"], + "unkey_id": "key_123456789", + "last_used_at": "2024-01-01T00:00:00.000Z", + "instance_id": "in_000000000000", + "created_at": "2021-01-01", + "updated_at": "2021-01-01", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_api_key, "error": None} + + response = await self.blindpay.instances.api_keys.get("ap_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_api_key + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/api-keys/ap_000000000000") + + @pytest.mark.asyncio + async def test_list_api_keys(self): + mocked_api_keys = [ + { + "id": "ap_000000000000", + "token": "token", + "name": "test", + "permission": "full_access", + "ip_whitelist": ["127.0.0.1"], + "unkey_id": "key_123456789", + "last_used_at": "2024-01-01T00:00:00.000Z", + "instance_id": "in_000000000000", + "created_at": "2021-01-01", + "updated_at": "2021-01-01", + }, + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_api_keys, "error": None} + + response = await self.blindpay.instances.api_keys.list() + + assert response["error"] is None + assert response["data"] == mocked_api_keys + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/api-keys") + + @pytest.mark.asyncio + async def test_delete_api_key(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = await self.blindpay.instances.api_keys.delete("ap_000000000000") + + assert response["error"] is None + assert response["data"] == {"data": None} + mock_request.assert_called_once_with("DELETE", "/instances/in_000000000000/api-keys/ap_000000000000", None) + + +class TestApiKeysSync: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPaySync(api_key="test-key", instance_id="in_000000000000") + + def test_create_api_key(self): + mocked_api_key = { + "id": "ap_000000000000", + "token": "token", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_api_key, "error": None} + + response = self.blindpay.instances.api_keys.create( + { + "name": "test", + "permission": "full_access", + "ip_whitelist": [], + } + ) + + assert response["error"] is None + assert response["data"] == mocked_api_key + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/api-keys", + {"name": "test", "permission": "full_access", "ip_whitelist": []}, + ) + + def test_get_api_key(self): + mocked_api_key = { + "id": "ap_000000000000", + "token": "token", + "name": "test", + "permission": "full_access", + "ip_whitelist": ["127.0.0.1"], + "unkey_id": "key_123456789", + "last_used_at": "2024-01-01T00:00:00.000Z", + "instance_id": "in_000000000000", + "created_at": "2021-01-01", + "updated_at": "2021-01-01", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_api_key, "error": None} + + response = self.blindpay.instances.api_keys.get("ap_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_api_key + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/api-keys/ap_000000000000") + + def test_list_api_keys(self): + mocked_api_keys = [ + { + "id": "ap_000000000000", + "token": "token", + "name": "test", + "permission": "full_access", + "ip_whitelist": ["127.0.0.1"], + "unkey_id": "key_123456789", + "last_used_at": "2024-01-01T00:00:00.000Z", + "instance_id": "in_000000000000", + "created_at": "2021-01-01", + "updated_at": "2021-01-01", + }, + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_api_keys, "error": None} + + response = self.blindpay.instances.api_keys.list() + + assert response["error"] is None + assert response["data"] == mocked_api_keys + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/api-keys") + + def test_delete_api_key(self): + """Test deleting an API key.""" + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = self.blindpay.instances.api_keys.delete("ap_000000000000") + + assert response["error"] is None + assert response["data"] == {"data": None} + mock_request.assert_called_once_with("DELETE", "/instances/in_000000000000/api-keys/ap_000000000000", None) diff --git a/tests/resources/test_available.py b/tests/resources/test_available.py new file mode 100644 index 0000000..abe88e4 --- /dev/null +++ b/tests/resources/test_available.py @@ -0,0 +1,230 @@ +from unittest.mock import patch + +import pytest + +from blindpay import BlindPay, BlindPaySync + + +class TestAvailable: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPay(api_key="test-key", instance_id="in_000000000000") + + @pytest.mark.asyncio + async def test_get_bank_details(self): + mocked_bank_details = [ + { + "label": "Account Type", + "regex": "", + "key": "account_type", + "items": [ + { + "label": "Checking", + "value": "checking", + }, + { + "label": "Savings", + "value": "saving", + }, + ], + "required": True, + }, + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_bank_details, "error": None} + + response = await self.blindpay.available.get_bank_details("pix") + + assert response["error"] is None + assert response["data"] == mocked_bank_details + mock_request.assert_called_once_with("GET", "/available/bank-details?rail=pix") + + @pytest.mark.asyncio + async def test_get_rails(self): + mocked_rails = [ + { + "label": "Domestic Wire", + "value": "wire", + "country": "US", + }, + { + "label": "ACH", + "value": "ach", + "country": "US", + }, + { + "label": "PIX", + "value": "pix", + "country": "BR", + }, + { + "label": "SPEI", + "value": "spei_bitso", + "country": "MX", + }, + { + "label": "Transfers 3.0", + "value": "transfers_bitso", + "country": "AR", + }, + { + "label": "ACH Colombia", + "value": "ach_cop_bitso", + "country": "CO", + }, + { + "label": "International Swift", + "value": "international_swift", + "country": "US", + }, + { + "label": "RTP", + "value": "rtp", + "country": "US", + }, + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_rails, "error": None} + + response = await self.blindpay.available.get_rails() + + assert response["error"] is None + assert response["data"] == mocked_rails + mock_request.assert_called_once_with("GET", "/available/rails") + + @pytest.mark.asyncio + async def test_get_swift_code_bank_details(self): + mocked_bank_details = [ + { + "id": "416", + "bank": "BANK OF AMERICA, N.A.", + "city": "NEW JERSEY", + "branch": "LENDING SERVICES AND OPERATIONS (LSOP)", + "swiftCode": "BOFAUS3NLMA", + "swiftCodeLink": "https://bank.codes/swift-code/united-states/bofaus3nlma/", + "country": "United States", + "countrySlug": "united-states", + }, + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_bank_details, "error": None} + + response = await self.blindpay.available.get_swift_code_bank_details("BOFAUS3NLMA") + + assert response["error"] is None + assert response["data"] == mocked_bank_details + mock_request.assert_called_once_with("GET", "/available/swift/BOFAUS3NLMA") + + +class TestAvailableSync: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPaySync(api_key="test-key", instance_id="in_000000000000") + + def test_get_bank_details(self): + mocked_bank_details = [ + { + "label": "Account Type", + "regex": "", + "key": "account_type", + "items": [ + { + "label": "Checking", + "value": "checking", + }, + { + "label": "Savings", + "value": "saving", + }, + ], + "required": True, + }, + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_bank_details, "error": None} + + response = self.blindpay.available.get_bank_details("pix") + + assert response["error"] is None + assert response["data"] == mocked_bank_details + mock_request.assert_called_once_with("GET", "/available/bank-details?rail=pix") + + def test_get_rails(self): + mocked_rails = [ + { + "label": "Domestic Wire", + "value": "wire", + "country": "US", + }, + { + "label": "ACH", + "value": "ach", + "country": "US", + }, + { + "label": "PIX", + "value": "pix", + "country": "BR", + }, + { + "label": "SPEI", + "value": "spei_bitso", + "country": "MX", + }, + { + "label": "Transfers 3.0", + "value": "transfers_bitso", + "country": "AR", + }, + { + "label": "ACH Colombia", + "value": "ach_cop_bitso", + "country": "CO", + }, + { + "label": "International Swift", + "value": "international_swift", + "country": "US", + }, + { + "label": "RTP", + "value": "rtp", + "country": "US", + }, + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_rails, "error": None} + + response = self.blindpay.available.get_rails() + + assert response["error"] is None + assert response["data"] == mocked_rails + mock_request.assert_called_once_with("GET", "/available/rails") + + def test_get_swift_code_bank_details(self): + mocked_bank_details = [ + { + "id": "416", + "bank": "BANK OF AMERICA, N.A.", + "city": "NEW JERSEY", + "branch": "LENDING SERVICES AND OPERATIONS (LSOP)", + "swiftCode": "BOFAUS3NLMA", + "swiftCodeLink": "https://bank.codes/swift-code/united-states/bofaus3nlma/", + "country": "United States", + "countrySlug": "united-states", + }, + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_bank_details, "error": None} + + response = self.blindpay.available.get_swift_code_bank_details("BOFAUS3NLMA") + + assert response["error"] is None + assert response["data"] == mocked_bank_details + mock_request.assert_called_once_with("GET", "/available/swift/BOFAUS3NLMA") diff --git a/tests/resources/test_bank_accounts.py b/tests/resources/test_bank_accounts.py new file mode 100644 index 0000000..39681dc --- /dev/null +++ b/tests/resources/test_bank_accounts.py @@ -0,0 +1,872 @@ +from unittest.mock import patch + +import pytest + +from blindpay import BlindPay, BlindPaySync + + +class TestBankAccounts: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPay(api_key="test-key", instance_id="in_000000000000") + + @pytest.mark.asyncio + async def test_create_pix_bank_account(self): + mocked_pix_account = { + "id": "ba_000000000000", + "type": "pix", + "name": "PIX Account", + "pix_key": "14947677768", + "created_at": "2021-01-01T00:00:00Z", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_pix_account, "error": None} + + response = await self.blindpay.receivers.bank_accounts.create_pix( + { + "receiver_id": "re_000000000000", + "name": "PIX Account", + "pix_key": "14947677768", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_pix_account + + @pytest.mark.asyncio + async def test_create_argentina_transfers_bank_account(self): + mocked_argentina_transfers_account = { + "id": "ba_000000000000", + "type": "transfers_bitso", + "name": "Argentina Transfers Account", + "beneficiary_name": "Individual full name or business name", + "transfers_type": "CVU", + "transfers_account": "BM123123123123", + "created_at": "2021-01-01T00:00:00Z", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_argentina_transfers_account, "error": None} + + response = await self.blindpay.receivers.bank_accounts.create_argentina_transfers( + { + "receiver_id": "re_000000000000", + "name": "Argentina Transfers Account", + "beneficiary_name": "Individual full name or business name", + "transfers_type": "CVU", + "transfers_account": "BM123123123123", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_argentina_transfers_account + + @pytest.mark.asyncio + async def test_create_spei_bank_account(self): + mocked_spei_account = { + "id": "ba_000000000000", + "type": "spei_bitso", + "name": "SPEI Account", + "beneficiary_name": "Individual full name or business name", + "spei_protocol": "clabe", + "spei_institution_code": "40002", + "spei_clabe": "5482347403740546", + "created_at": "2021-01-01T00:00:00Z", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_spei_account, "error": None} + + response = await self.blindpay.receivers.bank_accounts.create_spei( + { + "receiver_id": "re_000000000000", + "name": "SPEI Account", + "beneficiary_name": "Individual full name or business name", + "spei_protocol": "clabe", + "spei_institution_code": "40002", + "spei_clabe": "5482347403740546", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_spei_account + + @pytest.mark.asyncio + async def test_create_colombia_ach_bank_account(self): + mocked_colombia_ach_account = { + "id": "ba_000000000000", + "type": "ach_cop_bitso", + "name": "Colombia ACH Account", + "account_type": "checking", + "ach_cop_beneficiary_first_name": "Fernando", + "ach_cop_beneficiary_last_name": "Guzman Alarcón", + "ach_cop_document_id": "1661105408", + "ach_cop_document_type": "CC", + "ach_cop_email": "fernando.guzman@gmail.com", + "ach_cop_bank_code": "051", + "ach_cop_bank_account": "12345678", + "created_at": "2021-01-01T00:00:00Z", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_colombia_ach_account, "error": None} + + response = await self.blindpay.receivers.bank_accounts.create_colombia_ach( + { + "receiver_id": "re_000000000000", + "name": "Colombia ACH Account", + "account_type": "checking", + "ach_cop_beneficiary_first_name": "Fernando", + "ach_cop_beneficiary_last_name": "Guzman Alarcón", + "ach_cop_document_id": "1661105408", + "ach_cop_document_type": "CC", + "ach_cop_email": "fernando.guzman@gmail.com", + "ach_cop_bank_code": "051", + "ach_cop_bank_account": "12345678", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_colombia_ach_account + + @pytest.mark.asyncio + async def test_create_ach_bank_account(self): + mocked_ach_account = { + "id": "ba_000000000000", + "type": "ach", + "name": "ACH Account", + "beneficiary_name": "Individual full name or business name", + "routing_number": "012345678", + "account_number": "1001001234", + "account_type": "checking", + "account_class": "individual", + "address_line_1": None, + "address_line_2": None, + "city": None, + "state_province_region": None, + "country": None, + "postal_code": None, + "ach_cop_beneficiary_first_name": None, + "ach_cop_beneficiary_last_name": None, + "ach_cop_document_id": None, + "ach_cop_document_type": None, + "ach_cop_email": None, + "ach_cop_bank_code": None, + "ach_cop_bank_account": None, + "created_at": "2021-01-01T00:00:00Z", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_ach_account, "error": None} + + response = await self.blindpay.receivers.bank_accounts.create_ach( + { + "receiver_id": "re_000000000000", + "name": "ACH Account", + "account_class": "individual", + "account_number": "1001001234", + "account_type": "checking", + "beneficiary_name": "Individual full name or business name", + "routing_number": "012345678", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_ach_account + + @pytest.mark.asyncio + async def test_create_wire_bank_account(self): + mocked_wire_account = { + "id": "ba_000000000000", + "type": "wire", + "name": "Wire Account", + "beneficiary_name": "Individual full name or business name", + "routing_number": "012345678", + "account_number": "1001001234", + "address_line_1": "Address line 1", + "address_line_2": "Address line 2", + "city": "City", + "state_province_region": "State/Province/Region", + "country": "US", + "postal_code": "Postal code", + "created_at": "2021-01-01T00:00:00Z", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_wire_account, "error": None} + + response = await self.blindpay.receivers.bank_accounts.create_wire( + { + "receiver_id": "re_000000000000", + "name": "Wire Account", + "account_number": "1001001234", + "beneficiary_name": "Individual full name or business name", + "routing_number": "012345678", + "address_line_1": "Address line 1", + "address_line_2": "Address line 2", + "city": "City", + "state_province_region": "State/Province/Region", + "country": "US", + "postal_code": "Postal code", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_wire_account + + @pytest.mark.asyncio + async def test_create_international_swift_bank_account(self): + mocked_international_swift_account = { + "id": "ba_000000000000", + "type": "international_swift", + "name": "International Swift Account", + "beneficiary_name": None, + "address_line_1": None, + "address_line_2": None, + "city": None, + "state_province_region": None, + "country": None, + "postal_code": None, + "swift_code_bic": "123456789", + "swift_account_holder_name": "John Doe", + "swift_account_number_iban": "123456789", + "swift_beneficiary_address_line_1": ("123 Main Street, Suite 100, Downtown District, City Center CP 12345"), + "swift_beneficiary_address_line_2": None, + "swift_beneficiary_country": "MX", + "swift_beneficiary_city": "City", + "swift_beneficiary_state_province_region": "District", + "swift_beneficiary_postal_code": "11530", + "swift_bank_name": "Banco Regional SA", + "swift_bank_address_line_1": "123 Main Street, Suite 100, Downtown District, City Center CP 12345", + "swift_bank_address_line_2": None, + "swift_bank_country": "MX", + "swift_bank_city": "City", + "swift_bank_state_province_region": "District", + "swift_bank_postal_code": "11530", + "swift_intermediary_bank_swift_code_bic": None, + "swift_intermediary_bank_account_number_iban": None, + "swift_intermediary_bank_name": None, + "swift_intermediary_bank_country": None, + "created_at": "2021-01-01T00:00:00Z", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_international_swift_account, "error": None} + + response = await self.blindpay.receivers.bank_accounts.create_international_swift( + { + "receiver_id": "re_000000000000", + "name": "International Swift Account", + "swift_account_holder_name": "John Doe", + "swift_account_number_iban": "123456789", + "swift_bank_address_line_1": "123 Main Street, Suite 100, Downtown District, City Center CP 12345", + "swift_bank_address_line_2": None, + "swift_bank_city": "City", + "swift_bank_country": "MX", + "swift_bank_name": "Banco Regional SA", + "swift_bank_postal_code": "11530", + "swift_bank_state_province_region": "District", + "swift_beneficiary_address_line_1": ( + "123 Main Street, Suite 100, Downtown District, City Center CP 12345" + ), + "swift_beneficiary_address_line_2": None, + "swift_beneficiary_city": "City", + "swift_beneficiary_country": "MX", + "swift_beneficiary_postal_code": "11530", + "swift_beneficiary_state_province_region": "District", + "swift_code_bic": "123456789", + "swift_intermediary_bank_account_number_iban": None, + "swift_intermediary_bank_country": None, + "swift_intermediary_bank_name": None, + "swift_intermediary_bank_swift_code_bic": None, + } + ) + + assert response["error"] is None + assert response["data"] == mocked_international_swift_account + + @pytest.mark.asyncio + async def test_create_rtp_bank_account(self): + mocked_rtp_account = { + "id": "ba_JW5ZtlKMlgS1", + "type": "rtp", + "name": "John Doe RTP", + "beneficiary_name": "John Doe", + "routing_number": "121000358", + "account_number": "325203027578", + "address_line_1": "Street of the fools", + "address_line_2": None, + "city": "Fools City", + "state_province_region": "FL", + "country": "US", + "postal_code": "22599", + "created_at": "2025-09-30T04:23:30.823Z", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_rtp_account, "error": None} + + response = await self.blindpay.receivers.bank_accounts.create_rtp( + { + "receiver_id": "re_000000000000", + "name": "John Doe RTP", + "beneficiary_name": "John Doe", + "routing_number": "121000358", + "account_number": "325203027578", + "address_line_1": "Street of the fools", + "address_line_2": None, + "city": "Fools City", + "state_province_region": "FL", + "country": "US", + "postal_code": "22599", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_rtp_account + + @pytest.mark.asyncio + async def test_get_bank_account(self): + mocked_bank_account = { + "id": "ba_000000000000", + "receiver_id": "rcv_123", + "account_holder_name": "Individual full name or business name", + "account_number": "1001001234", + "routing_number": "012345678", + "account_type": "checking", + "bank_name": "Bank Name", + "swift_code": "123456789", + "iban": None, + "is_primary": False, + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_bank_account, "error": None} + + response = await self.blindpay.receivers.bank_accounts.get("re_000000000000", "ba_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_bank_account + + @pytest.mark.asyncio + async def test_list_bank_accounts(self): + mocked_bank_accounts = { + "data": [ + { + "id": "ba_000000000000", + "type": "wire", + "name": "Bank Account Name", + "pix_key": "14947677768", + "beneficiary_name": "Individual full name or business name", + "routing_number": "012345678", + "account_number": "1001001234", + "account_type": "checking", + "account_class": "individual", + "address_line_1": "Address line 1", + "address_line_2": "Address line 2", + "city": "City", + "state_province_region": "State/Province/Region", + "country": "US", + "postal_code": "Postal code", + "spei_protocol": "clabe", + "spei_institution_code": "40002", + "spei_clabe": "5482347403740546", + "transfers_type": "CVU", + "transfers_account": "BM123123123123", + "ach_cop_beneficiary_first_name": "Fernando", + "ach_cop_beneficiary_last_name": "Guzman Alarcón", + "ach_cop_document_id": "1661105408", + "ach_cop_document_type": "CC", + "ach_cop_email": "fernando.guzman@gmail.com", + "ach_cop_bank_code": "051", + "ach_cop_bank_account": "12345678", + "swift_code_bic": "123456789", + "swift_account_holder_name": "John Doe", + "swift_account_number_iban": "123456789", + "swift_beneficiary_address_line_1": ( + "123 Main Street, Suite 100, Downtown District, City Center CP 12345" + ), + "swift_beneficiary_address_line_2": ( + "456 Oak Avenue, Building 7, Financial District, Business Center CP 54321" + ), + "swift_beneficiary_country": "MX", + "swift_beneficiary_city": "City", + "swift_beneficiary_state_province_region": "District", + "swift_beneficiary_postal_code": "11530", + "swift_bank_name": "Banco Regional SA", + "swift_bank_address_line_1": ( + "123 Main Street, Suite 100, Downtown District, City Center CP 12345" + ), + "swift_bank_address_line_2": ( + "456 Oak Avenue, Building 7, Financial District, Business Center CP 54321" + ), + "swift_bank_country": "MX", + "swift_bank_city": "City", + "swift_bank_state_province_region": "District", + "swift_bank_postal_code": "11530", + "swift_intermediary_bank_swift_code_bic": "AEIBARB1", + "swift_intermediary_bank_account_number_iban": "123456789", + "swift_intermediary_bank_name": "Banco Regional SA", + "swift_intermediary_bank_country": "US", + "tron_wallet_hash": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", + "offramp_wallets": [ + { + "address": "TALJN9zTTEL9TVBb4WuTt6wLvPqJZr3hvb", + "id": "ow_000000000000", + "network": "tron", + "external_id": "your_external_id", + }, + ], + "created_at": "2021-01-01T00:00:00Z", + }, + ], + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_bank_accounts, "error": None} + + response = await self.blindpay.receivers.bank_accounts.list("re_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_bank_accounts + + @pytest.mark.asyncio + async def test_delete_bank_account(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = await self.blindpay.receivers.bank_accounts.delete("re_000000000000", "ba_000000000000") + + assert response["error"] is None + assert response["data"] == {"data": None} + + +class TestBankAccountsSync: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPaySync(api_key="test-key", instance_id="in_000000000000") + + def test_create_pix_bank_account(self): + mocked_pix_account = { + "id": "ba_000000000000", + "type": "pix", + "name": "PIX Account", + "pix_key": "14947677768", + "created_at": "2021-01-01T00:00:00Z", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_pix_account, "error": None} + + response = self.blindpay.receivers.bank_accounts.create_pix( + { + "receiver_id": "re_000000000000", + "name": "PIX Account", + "pix_key": "14947677768", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_pix_account + + def test_create_argentina_transfers_bank_account(self): + mocked_argentina_transfers_account = { + "id": "ba_000000000000", + "type": "transfers_bitso", + "name": "Argentina Transfers Account", + "beneficiary_name": "Individual full name or business name", + "transfers_type": "CVU", + "transfers_account": "BM123123123123", + "created_at": "2021-01-01T00:00:00Z", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_argentina_transfers_account, "error": None} + + response = self.blindpay.receivers.bank_accounts.create_argentina_transfers( + { + "receiver_id": "re_000000000000", + "name": "Argentina Transfers Account", + "beneficiary_name": "Individual full name or business name", + "transfers_type": "CVU", + "transfers_account": "BM123123123123", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_argentina_transfers_account + + def test_create_spei_bank_account(self): + mocked_spei_account = { + "id": "ba_000000000000", + "type": "spei_bitso", + "name": "SPEI Account", + "beneficiary_name": "Individual full name or business name", + "spei_protocol": "clabe", + "spei_institution_code": "40002", + "spei_clabe": "5482347403740546", + "created_at": "2021-01-01T00:00:00Z", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_spei_account, "error": None} + + response = self.blindpay.receivers.bank_accounts.create_spei( + { + "receiver_id": "re_000000000000", + "name": "SPEI Account", + "beneficiary_name": "Individual full name or business name", + "spei_protocol": "clabe", + "spei_institution_code": "40002", + "spei_clabe": "5482347403740546", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_spei_account + + def test_create_colombia_ach_bank_account(self): + mocked_colombia_ach_account = { + "id": "ba_000000000000", + "type": "ach_cop_bitso", + "name": "Colombia ACH Account", + "account_type": "checking", + "ach_cop_beneficiary_first_name": "Fernando", + "ach_cop_beneficiary_last_name": "Guzman Alarcón", + "ach_cop_document_id": "1661105408", + "ach_cop_document_type": "CC", + "ach_cop_email": "fernando.guzman@gmail.com", + "ach_cop_bank_code": "051", + "ach_cop_bank_account": "12345678", + "created_at": "2021-01-01T00:00:00Z", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_colombia_ach_account, "error": None} + + response = self.blindpay.receivers.bank_accounts.create_colombia_ach( + { + "receiver_id": "re_000000000000", + "name": "Colombia ACH Account", + "account_type": "checking", + "ach_cop_beneficiary_first_name": "Fernando", + "ach_cop_beneficiary_last_name": "Guzman Alarcón", + "ach_cop_document_id": "1661105408", + "ach_cop_document_type": "CC", + "ach_cop_email": "fernando.guzman@gmail.com", + "ach_cop_bank_code": "051", + "ach_cop_bank_account": "12345678", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_colombia_ach_account + + def test_create_ach_bank_account(self): + mocked_ach_account = { + "id": "ba_000000000000", + "type": "ach", + "name": "ACH Account", + "beneficiary_name": "Individual full name or business name", + "routing_number": "012345678", + "account_number": "1001001234", + "account_type": "checking", + "account_class": "individual", + "address_line_1": None, + "address_line_2": None, + "city": None, + "state_province_region": None, + "country": None, + "postal_code": None, + "ach_cop_beneficiary_first_name": None, + "ach_cop_beneficiary_last_name": None, + "ach_cop_document_id": None, + "ach_cop_document_type": None, + "ach_cop_email": None, + "ach_cop_bank_code": None, + "ach_cop_bank_account": None, + "created_at": "2021-01-01T00:00:00Z", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_ach_account, "error": None} + + response = self.blindpay.receivers.bank_accounts.create_ach( + { + "receiver_id": "re_000000000000", + "name": "ACH Account", + "account_class": "individual", + "account_number": "1001001234", + "account_type": "checking", + "beneficiary_name": "Individual full name or business name", + "routing_number": "012345678", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_ach_account + + def test_create_wire_bank_account(self): + mocked_wire_account = { + "id": "ba_000000000000", + "type": "wire", + "name": "Wire Account", + "beneficiary_name": "Individual full name or business name", + "routing_number": "012345678", + "account_number": "1001001234", + "address_line_1": "Address line 1", + "address_line_2": "Address line 2", + "city": "City", + "state_province_region": "State/Province/Region", + "country": "US", + "postal_code": "Postal code", + "created_at": "2021-01-01T00:00:00Z", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_wire_account, "error": None} + + response = self.blindpay.receivers.bank_accounts.create_wire( + { + "receiver_id": "re_000000000000", + "name": "Wire Account", + "account_number": "1001001234", + "beneficiary_name": "Individual full name or business name", + "routing_number": "012345678", + "address_line_1": "Address line 1", + "address_line_2": "Address line 2", + "city": "City", + "state_province_region": "State/Province/Region", + "country": "US", + "postal_code": "Postal code", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_wire_account + + def test_create_international_swift_bank_account(self): + mocked_international_swift_account = { + "id": "ba_000000000000", + "type": "international_swift", + "name": "International Swift Account", + "beneficiary_name": None, + "address_line_1": None, + "address_line_2": None, + "city": None, + "state_province_region": None, + "country": None, + "postal_code": None, + "swift_code_bic": "123456789", + "swift_account_holder_name": "John Doe", + "swift_account_number_iban": "123456789", + "swift_beneficiary_address_line_1": ("123 Main Street, Suite 100, Downtown District, City Center CP 12345"), + "swift_beneficiary_address_line_2": None, + "swift_beneficiary_country": "MX", + "swift_beneficiary_city": "City", + "swift_beneficiary_state_province_region": "District", + "swift_beneficiary_postal_code": "11530", + "swift_bank_name": "Banco Regional SA", + "swift_bank_address_line_1": "123 Main Street, Suite 100, Downtown District, City Center CP 12345", + "swift_bank_address_line_2": None, + "swift_bank_country": "MX", + "swift_bank_city": "City", + "swift_bank_state_province_region": "District", + "swift_bank_postal_code": "11530", + "swift_intermediary_bank_swift_code_bic": None, + "swift_intermediary_bank_account_number_iban": None, + "swift_intermediary_bank_name": None, + "swift_intermediary_bank_country": None, + "created_at": "2021-01-01T00:00:00Z", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_international_swift_account, "error": None} + + response = self.blindpay.receivers.bank_accounts.create_international_swift( + { + "receiver_id": "re_000000000000", + "name": "International Swift Account", + "swift_account_holder_name": "John Doe", + "swift_account_number_iban": "123456789", + "swift_bank_address_line_1": "123 Main Street, Suite 100, Downtown District, City Center CP 12345", + "swift_bank_address_line_2": None, + "swift_bank_city": "City", + "swift_bank_country": "MX", + "swift_bank_name": "Banco Regional SA", + "swift_bank_postal_code": "11530", + "swift_bank_state_province_region": "District", + "swift_beneficiary_address_line_1": ( + "123 Main Street, Suite 100, Downtown District, City Center CP 12345" + ), + "swift_beneficiary_address_line_2": None, + "swift_beneficiary_city": "City", + "swift_beneficiary_country": "MX", + "swift_beneficiary_postal_code": "11530", + "swift_beneficiary_state_province_region": "District", + "swift_code_bic": "123456789", + "swift_intermediary_bank_account_number_iban": None, + "swift_intermediary_bank_country": None, + "swift_intermediary_bank_name": None, + "swift_intermediary_bank_swift_code_bic": None, + } + ) + + assert response["error"] is None + assert response["data"] == mocked_international_swift_account + + def test_create_rtp_bank_account(self): + mocked_rtp_account = { + "id": "ba_JW5ZtlKMlgS1", + "type": "rtp", + "name": "John Doe RTP", + "beneficiary_name": "John Doe", + "routing_number": "121000358", + "account_number": "325203027578", + "address_line_1": "Street of the fools", + "address_line_2": None, + "city": "Fools City", + "state_province_region": "FL", + "country": "US", + "postal_code": "22599", + "created_at": "2025-09-30T04:23:30.823Z", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_rtp_account, "error": None} + + response = self.blindpay.receivers.bank_accounts.create_rtp( + { + "receiver_id": "re_000000000000", + "name": "John Doe RTP", + "beneficiary_name": "John Doe", + "routing_number": "121000358", + "account_number": "325203027578", + "address_line_1": "Street of the fools", + "address_line_2": None, + "city": "Fools City", + "state_province_region": "FL", + "country": "US", + "postal_code": "22599", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_rtp_account + + def test_get_bank_account(self): + mocked_bank_account = { + "id": "ba_000000000000", + "receiver_id": "rcv_123", + "account_holder_name": "Individual full name or business name", + "account_number": "1001001234", + "routing_number": "012345678", + "account_type": "checking", + "bank_name": "Bank Name", + "swift_code": "123456789", + "iban": None, + "is_primary": False, + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_bank_account, "error": None} + + response = self.blindpay.receivers.bank_accounts.get("re_000000000000", "ba_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_bank_account + + def test_list_bank_accounts(self): + mocked_bank_accounts = { + "data": [ + { + "id": "ba_000000000000", + "type": "wire", + "name": "Bank Account Name", + "pix_key": "14947677768", + "beneficiary_name": "Individual full name or business name", + "routing_number": "012345678", + "account_number": "1001001234", + "account_type": "checking", + "account_class": "individual", + "address_line_1": "Address line 1", + "address_line_2": "Address line 2", + "city": "City", + "state_province_region": "State/Province/Region", + "country": "US", + "postal_code": "Postal code", + "spei_protocol": "clabe", + "spei_institution_code": "40002", + "spei_clabe": "5482347403740546", + "transfers_type": "CVU", + "transfers_account": "BM123123123123", + "ach_cop_beneficiary_first_name": "Fernando", + "ach_cop_beneficiary_last_name": "Guzman Alarcón", + "ach_cop_document_id": "1661105408", + "ach_cop_document_type": "CC", + "ach_cop_email": "fernando.guzman@gmail.com", + "ach_cop_bank_code": "051", + "ach_cop_bank_account": "12345678", + "swift_code_bic": "123456789", + "swift_account_holder_name": "John Doe", + "swift_account_number_iban": "123456789", + "swift_beneficiary_address_line_1": ( + "123 Main Street, Suite 100, Downtown District, City Center CP 12345" + ), + "swift_beneficiary_address_line_2": ( + "456 Oak Avenue, Building 7, Financial District, Business Center CP 54321" + ), + "swift_beneficiary_country": "MX", + "swift_beneficiary_city": "City", + "swift_beneficiary_state_province_region": "District", + "swift_beneficiary_postal_code": "11530", + "swift_bank_name": "Banco Regional SA", + "swift_bank_address_line_1": ( + "123 Main Street, Suite 100, Downtown District, City Center CP 12345" + ), + "swift_bank_address_line_2": ( + "456 Oak Avenue, Building 7, Financial District, Business Center CP 54321" + ), + "swift_bank_country": "MX", + "swift_bank_city": "City", + "swift_bank_state_province_region": "District", + "swift_bank_postal_code": "11530", + "swift_intermediary_bank_swift_code_bic": "AEIBARB1", + "swift_intermediary_bank_account_number_iban": "123456789", + "swift_intermediary_bank_name": "Banco Regional SA", + "swift_intermediary_bank_country": "US", + "tron_wallet_hash": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", + "offramp_wallets": [ + { + "address": "TALJN9zTTEL9TVBb4WuTt6wLvPqJZr3hvb", + "id": "ow_000000000000", + "network": "tron", + "external_id": "your_external_id", + }, + ], + "created_at": "2021-01-01T00:00:00Z", + }, + ], + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_bank_accounts, "error": None} + + response = self.blindpay.receivers.bank_accounts.list("re_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_bank_accounts + + def test_delete_bank_account(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = self.blindpay.receivers.bank_accounts.delete("re_000000000000", "ba_000000000000") + + assert response["error"] is None + assert response["data"] == {"data": None} diff --git a/tests/resources/test_blockchain_wallets.py b/tests/resources/test_blockchain_wallets.py new file mode 100644 index 0000000..141b1ee --- /dev/null +++ b/tests/resources/test_blockchain_wallets.py @@ -0,0 +1,502 @@ +from unittest.mock import patch + +import pytest + +from blindpay import BlindPay, BlindPaySync + + +class TestBlockchainWallets: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPay(api_key="test-key", instance_id="in_000000000000") + + @pytest.mark.asyncio + async def test_get_blockchain_wallet_message(self): + mocked_message = {"message": "random"} + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_message, "error": None} + + response = await self.blindpay.wallets.blockchain.get_wallet_message("re_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_message + mock_request.assert_called_once_with( + "GET", "/instances/in_000000000000/receivers/re_000000000000/blockchain-wallets/sign-message" + ) + + @pytest.mark.asyncio + async def test_list_blockchain_wallets(self): + mocked_wallets = [ + { + "id": "bw_000000000000", + "name": "Wallet Display Name", + "network": "polygon", + "address": "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C", + "signature_tx_hash": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", + "is_account_abstraction": False, + "receiver_id": "re_000000000000", + } + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_wallets, "error": None} + + response = await self.blindpay.wallets.blockchain.list("re_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_wallets + mock_request.assert_called_once_with( + "GET", "/instances/in_000000000000/receivers/re_000000000000/blockchain-wallets" + ) + + @pytest.mark.asyncio + async def test_create_blockchain_wallet_with_address(self): + mocked_wallet = { + "id": "bw_000000000000", + "name": "Wallet Display Name", + "network": "polygon", + "address": "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C", + "signature_tx_hash": None, + "is_account_abstraction": True, + "receiver_id": "re_000000000000", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_wallet, "error": None} + + response = await self.blindpay.wallets.blockchain.create_with_address( + { + "receiver_id": "re_000000000000", + "name": "Wallet Display Name", + "network": "polygon", + "address": "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_wallet + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/receivers/re_000000000000/blockchain-wallets", + { + "name": "Wallet Display Name", + "network": "polygon", + "address": "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C", + "is_account_abstraction": True, + }, + ) + + @pytest.mark.asyncio + async def test_create_blockchain_wallet_with_hash(self): + mocked_wallet = { + "id": "bw_000000000000", + "name": "Wallet Display Name", + "network": "polygon", + "address": "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C", + "signature_tx_hash": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", + "is_account_abstraction": False, + "receiver_id": "re_000000000000", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_wallet, "error": None} + + response = await self.blindpay.wallets.blockchain.create_with_hash( + { + "receiver_id": "re_000000000000", + "name": "Wallet Display Name", + "network": "polygon", + "signature_tx_hash": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_wallet + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/receivers/re_000000000000/blockchain-wallets", + { + "name": "Wallet Display Name", + "network": "polygon", + "signature_tx_hash": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", + "is_account_abstraction": False, + }, + ) + + @pytest.mark.asyncio + async def test_get_blockchain_wallet(self): + mocked_wallet = { + "id": "bw_000000000000", + "name": "Wallet Display Name", + "network": "polygon", + "address": "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C", + "signature_tx_hash": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", + "is_account_abstraction": False, + "receiver_id": "re_000000000000", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_wallet, "error": None} + + response = await self.blindpay.wallets.blockchain.get( + { + "receiver_id": "re_000000000000", + "id": "bw_000000000000", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_wallet + mock_request.assert_called_once_with( + "GET", "/instances/in_000000000000/receivers/re_000000000000/blockchain-wallets/bw_000000000000" + ) + + @pytest.mark.asyncio + async def test_delete_blockchain_wallet(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = await self.blindpay.wallets.blockchain.delete( + { + "receiver_id": "re_000000000000", + "id": "bw_000000000000", + } + ) + + assert response["error"] is None + assert response["data"] == {"data": None} + mock_request.assert_called_once_with( + "DELETE", + "/instances/in_000000000000/receivers/re_000000000000/blockchain-wallets/bw_000000000000", + None, + ) + + @pytest.mark.asyncio + async def test_create_asset_trustline(self): + mocked_response = { + "xdr": ( + "AAAAAgAAAABqVFqpZzXx+KxRjXXFGO3sKwHCEYdHsWxDRrJTLGPDowAAAGQABVECAAAAAQAAAAEAAAAAAAAAAAAAAA" + "BmWFbUAAAAAAAAAAEAAAAAAAAABgAAAAFVU0RCAAAAAABbjPEfrLNLCLjNQyaWWgTeFn4tnbFnNd9FTJ3HgkLUCwAAAAAAAAAAAAAAAAAAAAE=" + ), + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_response, "error": None} + + response = await self.blindpay.wallets.blockchain.create_asset_trustline( + "GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B" + ) + + assert response["error"] is None + assert response["data"] == mocked_response + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/create-asset-trustline", + {"address": "GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B"}, + ) + + @pytest.mark.asyncio + async def test_mint_usdb_stellar(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {}, "error": None} + + response = await self.blindpay.wallets.blockchain.mint_usdb_stellar( + { + "address": "GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B", + "amount": "1000000", + "signedXdr": ( + "AAAAAgAAAABqVFqpZzXx+KxRjXXFGO3sKwHCEYdHsWxDRrJTLGPDowAAAGQABVECAAAAAQAAAAEAAAAAAAAAAAAA" + "AABmWFbUAAAAAAAAAAEAAAAAAAAABgAAAAFVU0RCAAAAAABbjPEfrLNLCLjNQyaWWgTeFn4tnbFnNd9FTJ3HgkLUCwAAAAAAAAAAAAAAAAAAAAE=" + ), + } + ) + + assert response["error"] is None + assert response["data"] is not None + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/mint-usdb-stellar", + { + "address": "GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B", + "amount": "1000000", + "signedXdr": ( + "AAAAAgAAAABqVFqpZzXx+KxRjXXFGO3sKwHCEYdHsWxDRrJTLGPDowAAAGQABVECAAAAAQAAAAEAAAAAAAAAAAAA" + "AABmWFbUAAAAAAAAAAEAAAAAAAAABgAAAAFVU0RCAAAAAABbjPEfrLNLCLjNQyaWWgTeFn4tnbFnNd9FTJ3HgkLUCwAAAAAAAAAAAAAAAAAAAAE=" + ), + }, + ) + + @pytest.mark.asyncio + async def test_mint_usdb_solana(self): + mocked_response = { + "success": True, + "signature": "4wceVEQeJG4vpS4k2o1dHU5cFWeWTQU8iaCEpRaV5KkqSxPfbdAc8hzXa7nNYG6rvqgAmDkzBycbcXkKKAeK8Jtu", + "error": "", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_response, "error": None} + + response = await self.blindpay.wallets.blockchain.mint_usdb_solana( + { + "address": "7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5", + "amount": "1000000", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_response + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/mint-usdb-solana", + { + "address": "7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5", + "amount": "1000000", + }, + ) + + @pytest.mark.asyncio + async def test_prepare_solana_delegation_transaction(self): + mocked_response = { + "success": True, + "transaction": "AAGBf4K95Gp5i6f0BAEYAgABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIw==", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_response, "error": None} + + response = await self.blindpay.wallets.blockchain.prepare_solana_delegation_transaction( + { + "token_address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "amount": "1000000", + "owner_address": "7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_response + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/prepare-delegate-solana", + { + "token_address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "amount": "1000000", + "owner_address": "7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5", + }, + ) + + +class TestBlockchainWalletsSync: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPaySync(api_key="test-key", instance_id="in_000000000000") + + def test_get_blockchain_wallet_message(self): + mocked_message = {"message": "random"} + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_message, "error": None} + + response = self.blindpay.wallets.blockchain.get_wallet_message("re_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_message + mock_request.assert_called_once_with( + "GET", "/instances/in_000000000000/receivers/re_000000000000/blockchain-wallets/sign-message" + ) + + def test_list_blockchain_wallets(self): + mocked_wallets = [ + { + "id": "bw_000000000000", + "name": "Wallet Display Name", + "network": "polygon", + "address": "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C", + "signature_tx_hash": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", + "is_account_abstraction": False, + "receiver_id": "re_000000000000", + } + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_wallets, "error": None} + + response = self.blindpay.wallets.blockchain.list("re_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_wallets + mock_request.assert_called_once_with( + "GET", "/instances/in_000000000000/receivers/re_000000000000/blockchain-wallets" + ) + + def test_create_blockchain_wallet_with_address(self): + mocked_wallet = { + "id": "bw_000000000000", + "name": "Wallet Display Name", + "network": "polygon", + "address": "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C", + "signature_tx_hash": None, + "is_account_abstraction": True, + "receiver_id": "re_000000000000", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_wallet, "error": None} + + response = self.blindpay.wallets.blockchain.create_with_address( + { + "receiver_id": "re_000000000000", + "name": "Wallet Display Name", + "network": "polygon", + "address": "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_wallet + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/receivers/re_000000000000/blockchain-wallets", + { + "name": "Wallet Display Name", + "network": "polygon", + "address": "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C", + "is_account_abstraction": True, + }, + ) + + def test_create_blockchain_wallet_with_hash(self): + mocked_wallet = { + "id": "bw_000000000000", + "name": "Wallet Display Name", + "network": "polygon", + "address": "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C", + "signature_tx_hash": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", + "is_account_abstraction": False, + "receiver_id": "re_000000000000", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_wallet, "error": None} + + response = self.blindpay.wallets.blockchain.create_with_hash( + { + "receiver_id": "re_000000000000", + "name": "Wallet Display Name", + "network": "polygon", + "signature_tx_hash": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_wallet + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/receivers/re_000000000000/blockchain-wallets", + { + "name": "Wallet Display Name", + "network": "polygon", + "signature_tx_hash": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", + "is_account_abstraction": False, + }, + ) + + def test_get_blockchain_wallet(self): + mocked_wallet = { + "id": "bw_000000000000", + "name": "Wallet Display Name", + "network": "polygon", + "address": "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C", + "signature_tx_hash": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", + "is_account_abstraction": False, + "receiver_id": "re_000000000000", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_wallet, "error": None} + + response = self.blindpay.wallets.blockchain.get( + { + "receiver_id": "re_000000000000", + "id": "bw_000000000000", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_wallet + mock_request.assert_called_once_with( + "GET", "/instances/in_000000000000/receivers/re_000000000000/blockchain-wallets/bw_000000000000" + ) + + def test_delete_blockchain_wallet(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = self.blindpay.wallets.blockchain.delete( + { + "receiver_id": "re_000000000000", + "id": "bw_000000000000", + } + ) + + assert response["error"] is None + assert response["data"] == {"data": None} + mock_request.assert_called_once_with( + "DELETE", + "/instances/in_000000000000/receivers/re_000000000000/blockchain-wallets/bw_000000000000", + None, + ) + + def test_create_asset_trustline(self): + mocked_response = { + "xdr": ( + "AAAAAgAAAABqVFqpZzXx+KxRjXXFGO3sKwHCEYdHsWxDRrJTLGPDowAAAGQABVECAAAAAQAAAAEAAAAAAAAAAAAAAA" + "BmWFbUAAAAAAAAAAEAAAAAAAAABgAAAAFVU0RCAAAAAABbjPEfrLNLCLjNQyaWWgTeFn4tnbFnNd9FTJ3HgkLUCwAAAAAAAAAAAAAAAAAAAAE=" + ), + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_response, "error": None} + + response = self.blindpay.wallets.blockchain.create_asset_trustline( + "GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B" + ) + + assert response["error"] is None + assert response["data"] == mocked_response + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/create-asset-trustline", + {"address": "GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B"}, + ) + + def test_mint_usdb_stellar(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {}, "error": None} + + response = self.blindpay.wallets.blockchain.mint_usdb_stellar( + { + "address": "GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B", + "amount": "1000000", + "signedXdr": ( + "AAAAAgAAAABqVFqpZzXx+KxRjXXFGO3sKwHCEYdHsWxDRrJTLGPDowAAAGQABVECAAAAAQAAAAEAAAAAAAAAAAAA" + "AABmWFbUAAAAAAAAAAEAAAAAAAAABgAAAAFVU0RCAAAAAABbjPEfrLNLCLjNQyaWWgTeFn4tnbFnNd9FTJ3HgkLUCwAAAAAAAAAAAAAAAAAAAAE=" + ), + } + ) + + assert response["error"] is None + assert response["data"] is not None + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/mint-usdb-stellar", + { + "address": "GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B", + "amount": "1000000", + "signedXdr": ( + "AAAAAgAAAABqVFqpZzXx+KxRjXXFGO3sKwHCEYdHsWxDRrJTLGPDowAAAGQABVECAAAAAQAAAAEAAAAAAAAAAAAA" + "AABmWFbUAAAAAAAAAAEAAAAAAAAABgAAAAFVU0RCAAAAAABbjPEfrLNLCLjNQyaWWgTeFn4tnbFnNd9FTJ3HgkLUCwAAAAAAAAAAAAAAAAAAAAE=" + ), + }, + ) diff --git a/tests/resources/test_instances.py b/tests/resources/test_instances.py new file mode 100644 index 0000000..b872c4f --- /dev/null +++ b/tests/resources/test_instances.py @@ -0,0 +1,146 @@ +from unittest.mock import patch + +import pytest + +from blindpay import BlindPay, BlindPaySync + + +class TestInstances: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPay(api_key="test-key", instance_id="in_000000000000") + + @pytest.mark.asyncio + async def test_get_instance_members(self): + mocked_members = [ + { + "id": "us_000000000000", + "email": "email@example.com", + "first_name": "Harry", + "middle_name": "James", + "last_name": "Potter", + "image_url": "https://example.com/image.png", + "created_at": "2021-01-01T00:00:00Z", + "role": "admin", + }, + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_members, "error": None} + + response = await self.blindpay.instances.get_members() + + assert response["error"] is None + assert response["data"] == mocked_members + + @pytest.mark.asyncio + async def test_update_instance(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = await self.blindpay.instances.update( + { + "name": "New Instance Name", + } + ) + + assert response["error"] is None + assert response["data"] == {"data": None} + + @pytest.mark.asyncio + async def test_delete_instance(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = await self.blindpay.instances.delete() + + assert response["error"] is None + assert response["data"] == {"data": None} + + @pytest.mark.asyncio + async def test_delete_instance_member(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = await self.blindpay.instances.delete_member("us_000000000000") + + assert response["error"] is None + assert response["data"] == {"data": None} + + @pytest.mark.asyncio + async def test_update_instance_member_role(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = await self.blindpay.instances.update_member_role("us_000000000000", "checker") + + assert response["error"] is None + assert response["data"] == {"data": None} + + +class TestInstancesSync: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPaySync(api_key="test-key", instance_id="in_000000000000") + + def test_get_instance_members(self): + mocked_members = [ + { + "id": "us_000000000000", + "email": "email@example.com", + "first_name": "Harry", + "middle_name": "James", + "last_name": "Potter", + "image_url": "https://example.com/image.png", + "created_at": "2021-01-01T00:00:00Z", + "role": "admin", + }, + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_members, "error": None} + + response = self.blindpay.instances.get_members() + + assert response["error"] is None + assert response["data"] == mocked_members + + def test_update_instance(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = self.blindpay.instances.update( + { + "name": "New Instance Name", + } + ) + + assert response["error"] is None + assert response["data"] == {"data": None} + + def test_delete_instance(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = self.blindpay.instances.delete() + + assert response["error"] is None + assert response["data"] == {"data": None} + + def test_delete_instance_member(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = self.blindpay.instances.delete_member("us_000000000000") + + assert response["error"] is None + assert response["data"] == {"data": None} + + def test_update_instance_member_role(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = self.blindpay.instances.update_member_role("us_000000000000", "checker") + + assert response["error"] is None + assert response["data"] == {"data": None} diff --git a/tests/resources/test_offramp_wallets.py b/tests/resources/test_offramp_wallets.py new file mode 100644 index 0000000..18b923e --- /dev/null +++ b/tests/resources/test_offramp_wallets.py @@ -0,0 +1,208 @@ +from unittest.mock import patch + +import pytest + +from blindpay import BlindPay, BlindPaySync + + +class TestOfframpWallets: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPay(api_key="test-key", instance_id="in_000000000000") + + @pytest.mark.asyncio + async def test_list_offramp_wallets(self): + mocked_offramp_wallets = [ + { + "id": "ow_000000000000", + "external_id": "your_external_id", + "instance_id": "in_000000000000", + "receiver_id": "re_000000000000", + "bank_account_id": "ba_000000000000", + "network": "tron", + "address": "TALJN9zTTEL9TVBb4WuTt6wLvPqJZr3hvb", + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + } + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_offramp_wallets, "error": None} + + response = await self.blindpay.wallets.offramp.list( + { + "receiver_id": "re_000000000000", + "bank_account_id": "ba_000000000000", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_offramp_wallets + mock_request.assert_called_once_with( + "GET", + "/instances/in_000000000000/receivers/re_000000000000/bank-accounts/ba_000000000000/offramp-wallets", + ) + + @pytest.mark.asyncio + async def test_create_offramp_wallet(self): + mocked_offramp_wallet = { + "id": "ow_000000000000", + "external_id": "your_external_id", + "network": "tron", + "address": "TALJN9zTTEL9TVBb4WuTt6wLvPqJZr3hvb", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_offramp_wallet, "error": None} + + response = await self.blindpay.wallets.offramp.create( + { + "external_id": "your_external_id", + "network": "tron", + "receiver_id": "re_000000000000", + "bank_account_id": "ba_000000000000", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_offramp_wallet + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/receivers/re_000000000000/bank-accounts/ba_000000000000/offramp-wallets", + { + "external_id": "your_external_id", + "network": "tron", + }, + ) + + @pytest.mark.asyncio + async def test_get_offramp_wallet(self): + mocked_offramp_wallet = { + "id": "ow_000000000000", + "external_id": "your_external_id", + "instance_id": "in_000000000000", + "receiver_id": "re_000000000000", + "bank_account_id": "ba_000000000000", + "network": "tron", + "address": "TALJN9zTTEL9TVBb4WuTt6wLvPqJZr3hvb", + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_offramp_wallet, "error": None} + + response = await self.blindpay.wallets.offramp.get( + { + "id": "ow_000000000000", + "bank_account_id": "ba_000000000000", + "receiver_id": "re_000000000000", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_offramp_wallet + mock_request.assert_called_once_with( + "GET", + "/instances/in_000000000000/receivers/re_000000000000/bank-accounts/ba_000000000000/offramp-wallets/ow_000000000000", + ) + + +class TestOfframpWalletsSync: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPaySync(api_key="test-key", instance_id="in_000000000000") + + def test_list_offramp_wallets(self): + mocked_offramp_wallets = [ + { + "id": "ow_000000000000", + "external_id": "your_external_id", + "instance_id": "in_000000000000", + "receiver_id": "re_000000000000", + "bank_account_id": "ba_000000000000", + "network": "tron", + "address": "TALJN9zTTEL9TVBb4WuTt6wLvPqJZr3hvb", + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + } + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_offramp_wallets, "error": None} + + response = self.blindpay.wallets.offramp.list( + { + "receiver_id": "re_000000000000", + "bank_account_id": "ba_000000000000", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_offramp_wallets + mock_request.assert_called_once_with( + "GET", + "/instances/in_000000000000/receivers/re_000000000000/bank-accounts/ba_000000000000/offramp-wallets", + ) + + def test_create_offramp_wallet(self): + mocked_offramp_wallet = { + "id": "ow_000000000000", + "external_id": "your_external_id", + "network": "tron", + "address": "TALJN9zTTEL9TVBb4WuTt6wLvPqJZr3hvb", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_offramp_wallet, "error": None} + + response = self.blindpay.wallets.offramp.create( + { + "external_id": "your_external_id", + "network": "tron", + "receiver_id": "re_000000000000", + "bank_account_id": "ba_000000000000", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_offramp_wallet + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/receivers/re_000000000000/bank-accounts/ba_000000000000/offramp-wallets", + { + "external_id": "your_external_id", + "network": "tron", + }, + ) + + def test_get_offramp_wallet(self): + mocked_offramp_wallet = { + "id": "ow_000000000000", + "external_id": "your_external_id", + "instance_id": "in_000000000000", + "receiver_id": "re_000000000000", + "bank_account_id": "ba_000000000000", + "network": "tron", + "address": "TALJN9zTTEL9TVBb4WuTt6wLvPqJZr3hvb", + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_offramp_wallet, "error": None} + + response = self.blindpay.wallets.offramp.get( + { + "id": "ow_000000000000", + "bank_account_id": "ba_000000000000", + "receiver_id": "re_000000000000", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_offramp_wallet + mock_request.assert_called_once_with( + "GET", + "/instances/in_000000000000/receivers/re_000000000000/bank-accounts/ba_000000000000/offramp-wallets/ow_000000000000", + ) diff --git a/tests/resources/test_partner_fees.py b/tests/resources/test_partner_fees.py new file mode 100644 index 0000000..fdf613c --- /dev/null +++ b/tests/resources/test_partner_fees.py @@ -0,0 +1,191 @@ +from unittest.mock import patch + +import pytest + +from blindpay import BlindPay, BlindPaySync + + +class TestPartnerFees: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPay(api_key="test-key", instance_id="in_000000000000") + + @pytest.mark.asyncio + async def test_list_partner_fees(self): + mocked_list = [ + { + "id": "fe_000000000000", + "instance_id": "in_000000000000", + "name": "Display Name", + "payout_percentage_fee": 0, + "payout_flat_fee": 0, + "payin_percentage_fee": 0, + "payin_flat_fee": 0, + "evm_wallet_address": "0x1234567890123456789012345678901234567890", + "stellar_wallet_address": "GAB22222222222222222222222222222222222222222222222222222222222222", + }, + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_list, "error": None} + + response = await self.blindpay.partner_fees.list() + + assert response["error"] is None + assert response["data"] == mocked_list + + @pytest.mark.asyncio + async def test_create_partner_fee(self): + mock_partner_fee = { + "id": "fe_000000000000", + "instance_id": "in_000000000000", + "name": "Display Name", + "payout_percentage_fee": 0, + "payout_flat_fee": 0, + "payin_percentage_fee": 0, + "payin_flat_fee": 0, + "evm_wallet_address": "0x1234567890123456789012345678901234567890", + "stellar_wallet_address": "GAB22222222222222222222222222222222222222222222222222222222222222", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mock_partner_fee, "error": None} + + response = await self.blindpay.partner_fees.create( + { + "name": "Display Name", + "payout_percentage_fee": 0, + "payout_flat_fee": 0, + "payin_percentage_fee": 0, + "payin_flat_fee": 0, + "evm_wallet_address": "0x1234567890123456789012345678901234567890", + "stellar_wallet_address": "GAB22222222222222222222222222222222222222222222222222222222222222", + "virtual_account_set": False, + } + ) + + assert response["error"] is None + assert response["data"] == mock_partner_fee + + @pytest.mark.asyncio + async def test_get_partner_fee(self): + mocked_fee = { + "id": "fe_000000000000", + "instance_id": "in_000000000000", + "name": "Display Name", + "payout_percentage_fee": 0, + "payout_flat_fee": 0, + "payin_percentage_fee": 0, + "payin_flat_fee": 0, + "evm_wallet_address": "0x1234567890123456789012345678901234567890", + "stellar_wallet_address": "GAB22222222222222222222222222222222222222222222222222222222222222", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_fee, "error": None} + + response = await self.blindpay.partner_fees.get("fe_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_fee + + @pytest.mark.asyncio + async def test_delete_partner_fee(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = await self.blindpay.partner_fees.delete("fe_000000000000") + + assert response["error"] is None + assert response["data"] == {"data": None} + + +class TestPartnerFeesSync: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPaySync(api_key="test-key", instance_id="in_000000000000") + + def test_list_partner_fees(self): + mocked_list = [ + { + "id": "fe_000000000000", + "instance_id": "in_000000000000", + "name": "Display Name", + "payout_percentage_fee": 0, + "payout_flat_fee": 0, + "payin_percentage_fee": 0, + "payin_flat_fee": 0, + "evm_wallet_address": "0x1234567890123456789012345678901234567890", + "stellar_wallet_address": "GAB22222222222222222222222222222222222222222222222222222222222222", + }, + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_list, "error": None} + + response = self.blindpay.partner_fees.list() + + assert response["error"] is None + assert response["data"] == mocked_list + + def test_create_partner_fee(self): + mock_partner_fee = { + "id": "fe_000000000000", + "instance_id": "in_000000000000", + "name": "Display Name", + "payout_percentage_fee": 0, + "payout_flat_fee": 0, + "payin_percentage_fee": 0, + "payin_flat_fee": 0, + "evm_wallet_address": "0x1234567890123456789012345678901234567890", + "stellar_wallet_address": "GAB22222222222222222222222222222222222222222222222222222222222222", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mock_partner_fee, "error": None} + + response = self.blindpay.partner_fees.create( + { + "name": "Display Name", + "payout_percentage_fee": 0, + "payout_flat_fee": 0, + "payin_percentage_fee": 0, + "payin_flat_fee": 0, + "evm_wallet_address": "0x1234567890123456789012345678901234567890", + "stellar_wallet_address": "GAB22222222222222222222222222222222222222222222222222222222222222", + "virtual_account_set": False, + } + ) + + assert response["error"] is None + assert response["data"] == mock_partner_fee + + def test_get_partner_fee(self): + mocked_fee = { + "id": "fe_000000000000", + "instance_id": "in_000000000000", + "name": "Display Name", + "payout_percentage_fee": 0, + "payout_flat_fee": 0, + "payin_percentage_fee": 0, + "payin_flat_fee": 0, + "evm_wallet_address": "0x1234567890123456789012345678901234567890", + "stellar_wallet_address": "GAB22222222222222222222222222222222222222222222222222222222222222", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_fee, "error": None} + + response = self.blindpay.partner_fees.get("fe_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_fee + + def test_delete_partner_fee(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = self.blindpay.partner_fees.delete("fe_000000000000") + + assert response["error"] is None + assert response["data"] == {"data": None} diff --git a/tests/resources/test_payin_quotes.py b/tests/resources/test_payin_quotes.py new file mode 100644 index 0000000..4e1974c --- /dev/null +++ b/tests/resources/test_payin_quotes.py @@ -0,0 +1,185 @@ +from unittest.mock import patch + +import pytest + +from blindpay import BlindPay, BlindPaySync + + +class TestPayinQuotes: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPay(api_key="test-key", instance_id="in_000000000000") + + @pytest.mark.asyncio + async def test_create_payin_quote(self): + mocked_payin_quote = { + "id": "qu_000000000000", + "expires_at": 1712958191, + "commercial_quotation": 495, + "blindpay_quotation": 505, + "receiver_amount": 1010, + "sender_amount": 5240, + "partner_fee_amount": 150, + "flat_fee": 50, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_payin_quote, "error": None} + + response = await self.blindpay.payins.quotes.create( + { + "blockchain_wallet_id": "bw_000000000000", + "currency_type": "sender", + "cover_fees": True, + "request_amount": 1000, + "payment_method": "pix", + "token": "USDC", + "partner_fee_id": "pf_000000000000", + "payer_rules": { + "pix_allowed_tax_ids": ["149.476.037-68"], + }, + } + ) + + assert response["error"] is None + assert response["data"] == mocked_payin_quote + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/payin-quotes", + { + "blockchain_wallet_id": "bw_000000000000", + "currency_type": "sender", + "cover_fees": True, + "request_amount": 1000, + "payment_method": "pix", + "token": "USDC", + "partner_fee_id": "pf_000000000000", + "payer_rules": { + "pix_allowed_tax_ids": ["149.476.037-68"], + }, + }, + ) + + @pytest.mark.asyncio + async def test_get_fx_rate(self): + mocked_fx_rate = { + "commercial_quotation": 495, + "blindpay_quotation": 505, + "result_amount": 1, + "instance_flat_fee": 50, + "instance_percentage_fee": 0, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_fx_rate, "error": None} + + response = await self.blindpay.payins.quotes.get_fx_rate( + { + "currency_type": "sender", + "from_currency": "USD", + "to": "BRL", + "request_amount": 1000, + } + ) + + assert response["error"] is None + assert response["data"] == mocked_fx_rate + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/payin-quotes/fx", + { + "currency_type": "sender", + "from": "USD", + "to": "BRL", + "request_amount": 1000, + }, + ) + + +class TestPayinQuotesSync: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPaySync(api_key="test-key", instance_id="in_000000000000") + + def test_create_payin_quote(self): + mocked_payin_quote = { + "id": "qu_000000000000", + "expires_at": 1712958191, + "commercial_quotation": 495, + "blindpay_quotation": 505, + "receiver_amount": 1010, + "sender_amount": 5240, + "partner_fee_amount": 150, + "flat_fee": 50, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_payin_quote, "error": None} + + response = self.blindpay.payins.quotes.create( + { + "blockchain_wallet_id": "bw_000000000000", + "currency_type": "sender", + "cover_fees": True, + "request_amount": 1000, + "payment_method": "pix", + "token": "USDC", + "partner_fee_id": "pf_000000000000", + "payer_rules": { + "pix_allowed_tax_ids": ["149.476.037-68"], + }, + } + ) + + assert response["error"] is None + assert response["data"] == mocked_payin_quote + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/payin-quotes", + { + "blockchain_wallet_id": "bw_000000000000", + "currency_type": "sender", + "cover_fees": True, + "request_amount": 1000, + "payment_method": "pix", + "token": "USDC", + "partner_fee_id": "pf_000000000000", + "payer_rules": { + "pix_allowed_tax_ids": ["149.476.037-68"], + }, + }, + ) + + def test_get_fx_rate(self): + mocked_fx_rate = { + "commercial_quotation": 495, + "blindpay_quotation": 505, + "result_amount": 1, + "instance_flat_fee": 50, + "instance_percentage_fee": 0, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_fx_rate, "error": None} + + response = self.blindpay.payins.quotes.get_fx_rate( + { + "currency_type": "sender", + "from_currency": "USD", + "to": "BRL", + "request_amount": 1000, + } + ) + + assert response["error"] is None + assert response["data"] == mocked_fx_rate + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/payin-quotes/fx", + { + "currency_type": "sender", + "from": "USD", + "to": "BRL", + "request_amount": 1000, + }, + ) diff --git a/tests/resources/test_payins.py b/tests/resources/test_payins.py new file mode 100644 index 0000000..ad4df57 --- /dev/null +++ b/tests/resources/test_payins.py @@ -0,0 +1,898 @@ +from unittest.mock import patch + +import pytest + +from blindpay import BlindPay, BlindPaySync + + +class TestPayins: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPay(api_key="test-key", instance_id="in_000000000000") + + @pytest.mark.asyncio + async def test_list_payins(self): + mocked_payins = { + "data": [ + { + "receiver_id": "re_000000000000", + "id": "re_000000000000", + "pix_code": ( + "00020101021226790014br.gov.bcb.pix2557brcode.starkinfra.com/v2/" + "bcf07f6c4110454e9fd6f120bab13e835204000053039865802BR5915Blind Pay, Inc." + "6010Vila Velha62070503***6304BCAB" + ), + "memo_code": "8K45GHBNT6BQ6462", + "clabe": "014027000000000008", + "status": "processing", + "payin_quote_id": "pq_000000000000", + "instance_id": "in_000000000000", + "tracking_transaction": { + "step": "processing", + "status": "failed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_payment": { + "step": "on_hold", + "provider_name": "blockchain", + "provider_transaction_id": "tx_123456789", + "provider_status": "confirmed", + "estimated_time_of_arrival": "2011-10-05T15:00:00.000Z", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_complete": { + "step": "on_hold", + "status": "completed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_partner_fee": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + "image_url": "https://example.com/image.png", + "first_name": "John", + "last_name": "Doe", + "legal_name": "Company Name Inc.", + "type": "individual", + "payment_method": "pix", + "sender_amount": 5240, + "receiver_amount": 1010, + "token": "USDC", + "partner_fee_amount": 150, + "total_fee_amount": 1.53, + "commercial_quotation": 495, + "blindpay_quotation": 505, + "currency": "BRL", + "billing_fee": 100, + "name": "Wallet Display Name", + "address": "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C", + "network": "polygon", + "blindpay_bank_details": { + "routing_number": "121145349", + "account_number": "621327727210181", + "account_type": "Business checking", + "swift_bic_code": "CHASUS33", + "ach": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "wire": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "rtp": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "beneficiary": { + "name": "BlindPay, Inc.", + "address_line_1": "8 The Green", + "address_line_2": "Dover, DE 19901", + }, + "receiving_bank": { + "name": "Column NA - Brex", + "address_line_1": "1 Letterman Drive, Building A, Suite A4-700", + "address_line_2": "San Francisco, CA 94129", + }, + }, + }, + ], + "pagination": { + "has_more": True, + "next_page": 3, + "prev_page": 1, + }, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_payins, "error": None} + + response = await self.blindpay.payins.list() + + assert response["error"] is None + assert response["data"] == mocked_payins + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/payins") + + @pytest.mark.asyncio + async def test_get_payin(self): + mocked_payin = { + "receiver_id": "re_000000000000", + "id": "re_000000000000", + "pix_code": ( + "00020101021226790014br.gov.bcb.pix2557brcode.starkinfra.com/v2/" + "bcf07f6c4110454e9fd6f120bab13e835204000053039865802BR5915Blind Pay, Inc." + "6010Vila Velha62070503***6304BCAB" + ), + "memo_code": "8K45GHBNT6BQ6462", + "clabe": "014027000000000008", + "status": "processing", + "payin_quote_id": "pq_000000000000", + "instance_id": "in_000000000000", + "tracking_transaction": { + "step": "processing", + "status": "failed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_payment": { + "step": "on_hold", + "provider_name": "blockchain", + "provider_transaction_id": "tx_123456789", + "provider_status": "confirmed", + "estimated_time_of_arrival": "2011-10-05T15:00:00.000Z", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_complete": { + "step": "on_hold", + "status": "completed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_partner_fee": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + "image_url": "https://example.com/image.png", + "first_name": "John", + "last_name": "Doe", + "legal_name": "Company Name Inc.", + "type": "individual", + "payment_method": "pix", + "sender_amount": 5240, + "receiver_amount": 1010, + "token": "USDC", + "partner_fee_amount": 150, + "total_fee_amount": 1.53, + "commercial_quotation": 495, + "blindpay_quotation": 505, + "currency": "BRL", + "billing_fee": 100, + "name": "Wallet Display Name", + "address": "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C", + "network": "polygon", + "blindpay_bank_details": { + "routing_number": "121145349", + "account_number": "621327727210181", + "account_type": "Business checking", + "swift_bic_code": "CHASUS33", + "ach": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "wire": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "rtp": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "beneficiary": { + "name": "BlindPay, Inc.", + "address_line_1": "8 The Green", + "address_line_2": "Dover, DE 19901", + }, + "receiving_bank": { + "name": "Column NA - Brex", + "address_line_1": "1 Letterman Drive, Building A, Suite A4-700", + "address_line_2": "San Francisco, CA 94129", + }, + }, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_payin, "error": None} + + response = await self.blindpay.payins.get("pi_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_payin + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/payins/pi_000000000000") + + @pytest.mark.asyncio + async def test_export_payins(self): + mocked_export_payins = [ + { + "receiver_id": "re_000000000000", + "id": "re_000000000000", + "pix_code": ( + "00020101021226790014br.gov.bcb.pix2557brcode.starkinfra.com/v2/" + "bcf07f6c4110454e9fd6f120bab13e835204000053039865802BR5915Blind Pay, Inc." + "6010Vila Velha62070503***6304BCAB" + ), + "memo_code": "8K45GHBNT6BQ6462", + "clabe": "014027000000000008", + "status": "processing", + "payin_quote_id": "pq_000000000000", + "instance_id": "in_000000000000", + "tracking_transaction": { + "step": "processing", + "status": "failed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_payment": { + "step": "on_hold", + "provider_name": "blockchain", + "provider_transaction_id": "tx_123456789", + "provider_status": "confirmed", + "estimated_time_of_arrival": "2011-10-05T15:00:00.000Z", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_complete": { + "step": "on_hold", + "status": "completed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_partner_fee": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + "image_url": "https://example.com/image.png", + "first_name": "John", + "last_name": "Doe", + "legal_name": "Company Name Inc.", + "type": "individual", + "payment_method": "pix", + "sender_amount": 5240, + "receiver_amount": 1010, + "token": "USDC", + "partner_fee_amount": 150, + "total_fee_amount": 1.53, + "commercial_quotation": 495, + "blindpay_quotation": 505, + "currency": "BRL", + "billing_fee": 100, + "name": "Wallet Display Name", + "address": "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C", + "network": "polygon", + "blindpay_bank_details": { + "routing_number": "121145349", + "account_number": "621327727210181", + "account_type": "Business checking", + "swift_bic_code": "CHASUS33", + "ach": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "wire": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "rtp": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "beneficiary": { + "name": "BlindPay, Inc.", + "address_line_1": "8 The Green", + "address_line_2": "Dover, DE 19901", + }, + "receiving_bank": { + "name": "Column NA - Brex", + "address_line_1": "1 Letterman Drive, Building A, Suite A4-700", + "address_line_2": "San Francisco, CA 94129", + }, + }, + }, + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_export_payins, "error": None} + + response = await self.blindpay.payins.export( + { + "status": "processing", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_export_payins + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/export/payins?status=processing") + + @pytest.mark.asyncio + async def test_create_evm_payin(self): + mocked_evm_payin = { + "id": "pi_000000000000", + "status": "processing", + "pix_code": ( + "00020101021226790014br.gov.bcb.pix2557brcode.starkinfra.com/v2/" + "bcf07f6c4110454e9fd6f120bab13e835204000053039865802BR5915Blind Pay, Inc." + "6010Vila Velha62070503***6304BCAB" + ), + "memo_code": "8K45GHBNT6BQ6462", + "clabe": "014027000000000008", + "tracking_complete": { + "step": "on_hold", + "status": "completed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_payment": { + "step": "on_hold", + "provider_name": "blockchain", + "provider_transaction_id": "tx_123456789", + "provider_status": "confirmed", + "estimated_time_of_arrival": "2011-10-05T15:00:00.000Z", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_transaction": { + "step": "processing", + "status": "failed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_partner_fee": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "blindpay_bank_details": { + "routing_number": "121145349", + "account_number": "621327727210181", + "account_type": "Business checking", + "swift_bic_code": "CHASUS33", + "ach": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "wire": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "rtp": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "beneficiary": { + "name": "BlindPay, Inc.", + "address_line_1": "8 The Green", + "address_line_2": "Dover, DE 19901", + }, + "receiving_bank": { + "name": "Column NA - Brex", + "address_line_1": "1 Letterman Drive, Building A, Suite A4-700", + "address_line_2": "San Francisco, CA 94129", + }, + }, + "receiver_id": "re_000000000000", + "receiver_amount": 1010, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_evm_payin, "error": None} + + response = await self.blindpay.payins.create_evm("pq_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_evm_payin + mock_request.assert_called_once_with( + "POST", "/instances/in_000000000000/payins/evm", {"payin_quote_id": "pq_000000000000"} + ) + + +class TestPayinsSync: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPaySync(api_key="test-key", instance_id="in_000000000000") + + def test_list_payins(self): + mocked_payins = { + "data": [ + { + "receiver_id": "re_000000000000", + "id": "re_000000000000", + "pix_code": ( + "00020101021226790014br.gov.bcb.pix2557brcode.starkinfra.com/v2/" + "bcf07f6c4110454e9fd6f120bab13e835204000053039865802BR5915Blind Pay, Inc." + "6010Vila Velha62070503***6304BCAB" + ), + "memo_code": "8K45GHBNT6BQ6462", + "clabe": "014027000000000008", + "status": "processing", + "payin_quote_id": "pq_000000000000", + "instance_id": "in_000000000000", + "tracking_transaction": { + "step": "processing", + "status": "failed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_payment": { + "step": "on_hold", + "provider_name": "blockchain", + "provider_transaction_id": "tx_123456789", + "provider_status": "confirmed", + "estimated_time_of_arrival": "2011-10-05T15:00:00.000Z", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_complete": { + "step": "on_hold", + "status": "completed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_partner_fee": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + "image_url": "https://example.com/image.png", + "first_name": "John", + "last_name": "Doe", + "legal_name": "Company Name Inc.", + "type": "individual", + "payment_method": "pix", + "sender_amount": 5240, + "receiver_amount": 1010, + "token": "USDC", + "partner_fee_amount": 150, + "total_fee_amount": 1.53, + "commercial_quotation": 495, + "blindpay_quotation": 505, + "currency": "BRL", + "billing_fee": 100, + "name": "Wallet Display Name", + "address": "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C", + "network": "polygon", + "blindpay_bank_details": { + "routing_number": "121145349", + "account_number": "621327727210181", + "account_type": "Business checking", + "swift_bic_code": "CHASUS33", + "ach": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "wire": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "rtp": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "beneficiary": { + "name": "BlindPay, Inc.", + "address_line_1": "8 The Green", + "address_line_2": "Dover, DE 19901", + }, + "receiving_bank": { + "name": "Column NA - Brex", + "address_line_1": "1 Letterman Drive, Building A, Suite A4-700", + "address_line_2": "San Francisco, CA 94129", + }, + }, + }, + ], + "pagination": { + "has_more": True, + "next_page": 3, + "prev_page": 1, + }, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_payins, "error": None} + + response = self.blindpay.payins.list() + + assert response["error"] is None + assert response["data"] == mocked_payins + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/payins") + + def test_get_payin(self): + mocked_payin = { + "receiver_id": "re_000000000000", + "id": "re_000000000000", + "pix_code": ( + "00020101021226790014br.gov.bcb.pix2557brcode.starkinfra.com/v2/" + "bcf07f6c4110454e9fd6f120bab13e835204000053039865802BR5915Blind Pay, Inc." + "6010Vila Velha62070503***6304BCAB" + ), + "memo_code": "8K45GHBNT6BQ6462", + "clabe": "014027000000000008", + "status": "processing", + "payin_quote_id": "pq_000000000000", + "instance_id": "in_000000000000", + "tracking_transaction": { + "step": "processing", + "status": "failed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_payment": { + "step": "on_hold", + "provider_name": "blockchain", + "provider_transaction_id": "tx_123456789", + "provider_status": "confirmed", + "estimated_time_of_arrival": "2011-10-05T15:00:00.000Z", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_complete": { + "step": "on_hold", + "status": "completed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_partner_fee": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + "image_url": "https://example.com/image.png", + "first_name": "John", + "last_name": "Doe", + "legal_name": "Company Name Inc.", + "type": "individual", + "payment_method": "pix", + "sender_amount": 5240, + "receiver_amount": 1010, + "token": "USDC", + "partner_fee_amount": 150, + "total_fee_amount": 1.53, + "commercial_quotation": 495, + "blindpay_quotation": 505, + "currency": "BRL", + "billing_fee": 100, + "name": "Wallet Display Name", + "address": "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C", + "network": "polygon", + "blindpay_bank_details": { + "routing_number": "121145349", + "account_number": "621327727210181", + "account_type": "Business checking", + "swift_bic_code": "CHASUS33", + "ach": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "wire": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "rtp": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "beneficiary": { + "name": "BlindPay, Inc.", + "address_line_1": "8 The Green", + "address_line_2": "Dover, DE 19901", + }, + "receiving_bank": { + "name": "Column NA - Brex", + "address_line_1": "1 Letterman Drive, Building A, Suite A4-700", + "address_line_2": "San Francisco, CA 94129", + }, + }, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_payin, "error": None} + + response = self.blindpay.payins.get("pi_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_payin + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/payins/pi_000000000000") + + def test_get_payin_track(self): + mocked_payin_track = { + "receiver_id": "re_000000000000", + "id": "re_000000000000", + "pix_code": ( + "00020101021226790014br.gov.bcb.pix2557brcode.starkinfra.com/v2/" + "bcf07f6c4110454e9fd6f120bab13e835204000053039865802BR5915Blind Pay, Inc." + "6010Vila Velha62070503***6304BCAB" + ), + "memo_code": "8K45GHBNT6BQ6462", + "clabe": "014027000000000008", + "status": "processing", + "payin_quote_id": "pq_000000000000", + "instance_id": "in_000000000000", + "tracking_transaction": { + "step": "processing", + "status": "failed", + "external_id": "12345678", + "completed_at": "2011-10-05T14:48:00.000Z", + "sender_name": "John Doe Smith", + "sender_tax_id": "123.456.789-10", + "sender_bank_code": "00416968", + "sender_account_number": "1234567890", + "trace_number": "1234567890", + "transaction_reference": "1234567890", + "description": "Payment from John Doe Smith", + }, + "tracking_payment": { + "step": "on_hold", + "provider_name": "blockchain", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_complete": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_partner_fee": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + "image_url": "https://example.com/image.png", + "first_name": "John", + "last_name": "Doe", + "legal_name": "Company Name Inc.", + "type": "individual", + "payment_method": "pix", + "sender_amount": 5240, + "receiver_amount": 1010, + "token": "USDC", + "partner_fee_amount": 150, + "total_fee_amount": 1.53, + "commercial_quotation": 495, + "blindpay_quotation": 505, + "currency": "BRL", + "billing_fee": 100, + "name": "Wallet Display Name", + "address": "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C", + "network": "polygon", + "blindpay_bank_details": { + "routing_number": "121145349", + "account_number": "621327727210181", + "account_type": "Business checking", + "swift_bic_code": "CHASUS33", + "ach": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "wire": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "rtp": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "beneficiary": { + "name": "BlindPay, Inc.", + "address_line_1": "8 The Green", + "address_line_2": "Dover, DE 19901", + }, + "receiving_bank": { + "name": "Column NA - Brex", + "address_line_1": "1 Letterman Drive, Building A, Suite A4-700", + "address_line_2": "San Francisco, CA 94129", + }, + }, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_payin_track, "error": None} + + response = self.blindpay.payins.get_track("pi_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_payin_track + mock_request.assert_called_once_with("GET", "/e/payins/pi_000000000000") + + def test_export_payins(self): + mocked_export_payins = [ + { + "receiver_id": "re_000000000000", + "id": "re_000000000000", + "pix_code": ( + "00020101021226790014br.gov.bcb.pix2557brcode.starkinfra.com/v2/" + "bcf07f6c4110454e9fd6f120bab13e835204000053039865802BR5915Blind Pay, Inc." + "6010Vila Velha62070503***6304BCAB" + ), + "memo_code": "8K45GHBNT6BQ6462", + "clabe": "014027000000000008", + "status": "processing", + "payin_quote_id": "pq_000000000000", + "instance_id": "in_000000000000", + "tracking_transaction": { + "step": "processing", + "status": "failed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_payment": { + "step": "on_hold", + "provider_name": "blockchain", + "provider_transaction_id": "tx_123456789", + "provider_status": "confirmed", + "estimated_time_of_arrival": "2011-10-05T15:00:00.000Z", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_complete": { + "step": "on_hold", + "status": "completed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_partner_fee": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + "image_url": "https://example.com/image.png", + "first_name": "John", + "last_name": "Doe", + "legal_name": "Company Name Inc.", + "type": "individual", + "payment_method": "pix", + "sender_amount": 5240, + "receiver_amount": 1010, + "token": "USDC", + "partner_fee_amount": 150, + "total_fee_amount": 1.53, + "commercial_quotation": 495, + "blindpay_quotation": 505, + "currency": "BRL", + "billing_fee": 100, + "name": "Wallet Display Name", + "address": "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C", + "network": "polygon", + "blindpay_bank_details": { + "routing_number": "121145349", + "account_number": "621327727210181", + "account_type": "Business checking", + "swift_bic_code": "CHASUS33", + "ach": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "wire": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "rtp": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "beneficiary": { + "name": "BlindPay, Inc.", + "address_line_1": "8 The Green", + "address_line_2": "Dover, DE 19901", + }, + "receiving_bank": { + "name": "Column NA - Brex", + "address_line_1": "1 Letterman Drive, Building A, Suite A4-700", + "address_line_2": "San Francisco, CA 94129", + }, + }, + }, + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_export_payins, "error": None} + + response = self.blindpay.payins.export( + { + "status": "processing", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_export_payins + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/export/payins?status=processing") + + def test_create_evm_payin(self): + mocked_evm_payin = { + "id": "pi_000000000000", + "status": "processing", + "pix_code": ( + "00020101021226790014br.gov.bcb.pix2557brcode.starkinfra.com/v2/" + "bcf07f6c4110454e9fd6f120bab13e835204000053039865802BR5915Blind Pay, Inc." + "6010Vila Velha62070503***6304BCAB" + ), + "memo_code": "8K45GHBNT6BQ6462", + "clabe": "014027000000000008", + "tracking_complete": { + "step": "on_hold", + "status": "completed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_payment": { + "step": "on_hold", + "provider_name": "blockchain", + "provider_transaction_id": "tx_123456789", + "provider_status": "confirmed", + "estimated_time_of_arrival": "2011-10-05T15:00:00.000Z", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_transaction": { + "step": "processing", + "status": "failed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_partner_fee": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "blindpay_bank_details": { + "routing_number": "121145349", + "account_number": "621327727210181", + "account_type": "Business checking", + "swift_bic_code": "CHASUS33", + "ach": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "wire": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "rtp": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "beneficiary": { + "name": "BlindPay, Inc.", + "address_line_1": "8 The Green", + "address_line_2": "Dover, DE 19901", + }, + "receiving_bank": { + "name": "Column NA - Brex", + "address_line_1": "1 Letterman Drive, Building A, Suite A4-700", + "address_line_2": "San Francisco, CA 94129", + }, + }, + "receiver_id": "re_000000000000", + "receiver_amount": 1010, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_evm_payin, "error": None} + + response = self.blindpay.payins.create_evm("pq_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_evm_payin + mock_request.assert_called_once_with( + "POST", "/instances/in_000000000000/payins/evm", {"payin_quote_id": "pq_000000000000"} + ) diff --git a/tests/resources/test_payouts.py b/tests/resources/test_payouts.py new file mode 100644 index 0000000..a39bcd2 --- /dev/null +++ b/tests/resources/test_payouts.py @@ -0,0 +1,1170 @@ +from unittest.mock import patch + +import pytest + +from blindpay import BlindPay, BlindPaySync + + +class TestPayouts: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPay(api_key="test-key", instance_id="in_000000000000") + + @pytest.mark.asyncio + async def test_list_payouts(self): + mocked_payouts = { + "data": [ + { + "receiver_id": "re_000000000000", + "id": "pa_000000000000", + "status": "processing", + "sender_wallet_address": "0x123...890", + "signed_transaction": "AAA...Zey8y0A", + "quote_id": "qu_000000000000", + "instance_id": "in_000000000000", + "tracking_transaction": { + "step": "processing", + "status": "failed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_payment": { + "step": "on_hold", + "provider_name": "blockchain", + "provider_transaction_id": "0x123...890", + "provider_status": "canceled", + "estimated_time_of_arrival": "5_min", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_liquidity": { + "step": "processing", + "provider_transaction_id": "0x123...890", + "provider_status": "deposited", + "estimated_time_of_arrival": "1_business_day", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_complete": { + "step": "on_hold", + "status": "tokens_refunded", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_partner_fee": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + "image_url": "https://example.com/image.png", + "first_name": "John", + "last_name": "Doe", + "legal_name": "Company Name Inc.", + "network": "sepolia", + "token": "USDC", + "description": "Memo code or description, only works with USD and BRL", + "sender_amount": 1010, + "receiver_amount": 5240, + "partner_fee_amount": 150, + "commercial_quotation": 495, + "blindpay_quotation": 485, + "total_fee_amount": 1.5, + "receiver_local_amount": 1000, + "currency": "BRL", + "transaction_document_file": "https://example.com/image.png", + "transaction_document_type": "invoice", + "transaction_document_id": "1234567890", + "name": "Bank Account Name", + "type": "wire", + "pix_key": "14947677768", + "account_number": "1001001234", + "routing_number": "012345678", + "country": "US", + "account_class": "individual", + "address_line_1": "Address line 1", + "address_line_2": "Address line 2", + "city": "City", + "state_province_region": "State/Province/Region", + "postal_code": "Postal code", + "account_type": "checking", + "ach_cop_beneficiary_first_name": "Fernando", + "ach_cop_bank_account": "12345678", + "ach_cop_bank_code": "051", + "ach_cop_beneficiary_last_name": "Guzman Alarcón", + "ach_cop_document_id": "1661105408", + "ach_cop_document_type": "CC", + "ach_cop_email": "fernando.guzman@gmail.com", + "beneficiary_name": "Individual full name or business name", + "spei_clabe": "5482347403740546", + "spei_protocol": "clabe", + "spei_institution_code": "40002", + "swift_beneficiary_country": "MX", + "swift_code_bic": "123456789", + "swift_account_holder_name": "John Doe", + "swift_account_number_iban": "123456789", + "transfers_account": "BM123123123123", + "transfers_type": "CVU", + "has_virtual_account": True, + }, + ], + "pagination": { + "has_more": True, + "next_page": 3, + "prev_page": 1, + }, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_payouts, "error": None} + + response = await self.blindpay.payouts.list() + + assert response["error"] is None + assert response["data"] == mocked_payouts + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/payouts") + + @pytest.mark.asyncio + async def test_get_payout(self): + mocked_payout = { + "receiver_id": "re_000000000000", + "id": "pa_000000000000", + "status": "processing", + "sender_wallet_address": "0x123...890", + "signed_transaction": "AAA...Zey8y0A", + "quote_id": "qu_000000000000", + "instance_id": "in_000000000000", + "tracking_transaction": { + "step": "processing", + "status": "failed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_payment": { + "step": "on_hold", + "provider_name": "blockchain", + "provider_transaction_id": "0x123...890", + "provider_status": "canceled", + "estimated_time_of_arrival": "5_min", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_liquidity": { + "step": "processing", + "provider_transaction_id": "0x123...890", + "provider_status": "deposited", + "estimated_time_of_arrival": "1_business_day", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_complete": { + "step": "on_hold", + "status": "tokens_refunded", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_partner_fee": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + "image_url": "https://example.com/image.png", + "first_name": "John", + "last_name": "Doe", + "legal_name": "Company Name Inc.", + "network": "sepolia", + "token": "USDC", + "description": "Memo code or description, only works with USD and BRL", + "sender_amount": 1010, + "receiver_amount": 5240, + "partner_fee_amount": 150, + "commercial_quotation": 495, + "blindpay_quotation": 485, + "total_fee_amount": 1.5, + "receiver_local_amount": 1000, + "currency": "BRL", + "transaction_document_file": "https://example.com/image.png", + "transaction_document_type": "invoice", + "transaction_document_id": "1234567890", + "name": "Bank Account Name", + "type": "wire", + "pix_key": "14947677768", + "account_number": "1001001234", + "routing_number": "012345678", + "country": "US", + "account_class": "individual", + "address_line_1": "Address line 1", + "address_line_2": "Address line 2", + "city": "City", + "state_province_region": "State/Province/Region", + "postal_code": "Postal code", + "account_type": "checking", + "ach_cop_beneficiary_first_name": "Fernando", + "ach_cop_bank_account": "12345678", + "ach_cop_bank_code": "051", + "ach_cop_beneficiary_last_name": "Guzman Alarcón", + "ach_cop_document_id": "1661105408", + "ach_cop_document_type": "CC", + "ach_cop_email": "fernando.guzman@gmail.com", + "beneficiary_name": "Individual full name or business name", + "spei_clabe": "5482347403740546", + "spei_protocol": "clabe", + "spei_institution_code": "40002", + "swift_beneficiary_country": "MX", + "swift_code_bic": "123456789", + "swift_account_holder_name": "John Doe", + "swift_account_number_iban": "123456789", + "transfers_account": "BM123123123123", + "transfers_type": "CVU", + "has_virtual_account": True, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_payout, "error": None} + + response = await self.blindpay.payouts.get("pa_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_payout + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/payouts/pa_000000000000") + + @pytest.mark.asyncio + async def test_export_payouts(self): + mocked_export_payouts = [ + { + "receiver_id": "re_000000000000", + "id": "pa_000000000000", + "status": "processing", + "sender_wallet_address": "0x123...890", + "signed_transaction": "AAA...Zey8y0A", + "quote_id": "qu_000000000000", + "instance_id": "in_000000000000", + "tracking_transaction": { + "step": "processing", + "status": "failed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_payment": { + "step": "on_hold", + "provider_name": "blockchain", + "provider_transaction_id": "0x123...890", + "provider_status": "canceled", + "estimated_time_of_arrival": "5_min", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_liquidity": { + "step": "processing", + "provider_transaction_id": "0x123...890", + "provider_status": "deposited", + "estimated_time_of_arrival": "1_business_day", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_complete": { + "step": "on_hold", + "status": "tokens_refunded", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_partner_fee": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + "image_url": "https://example.com/image.png", + "first_name": "John", + "last_name": "Doe", + "legal_name": "Company Name Inc.", + "network": "sepolia", + "token": "USDC", + "description": "Memo code or description, only works with USD and BRL", + "sender_amount": 1010, + "receiver_amount": 5240, + "partner_fee_amount": 150, + "commercial_quotation": 495, + "blindpay_quotation": 485, + "total_fee_amount": 1.5, + "receiver_local_amount": 1000, + "currency": "BRL", + "transaction_document_file": "https://example.com/image.png", + "transaction_document_type": "invoice", + "transaction_document_id": "1234567890", + "name": "Bank Account Name", + "type": "wire", + "pix_key": "14947677768", + "account_number": "1001001234", + "routing_number": "012345678", + "country": "US", + "account_class": "individual", + "address_line_1": "Address line 1", + "address_line_2": "Address line 2", + "city": "City", + "state_province_region": "State/Province/Region", + "postal_code": "Postal code", + "account_type": "checking", + "ach_cop_beneficiary_first_name": "Fernando", + "ach_cop_bank_account": "12345678", + "ach_cop_bank_code": "051", + "ach_cop_beneficiary_last_name": "Guzman Alarcón", + "ach_cop_document_id": "1661105408", + "ach_cop_document_type": "CC", + "ach_cop_email": "fernando.guzman@gmail.com", + "beneficiary_name": "Individual full name or business name", + "spei_clabe": "5482347403740546", + "spei_protocol": "clabe", + "spei_institution_code": "40002", + "swift_beneficiary_country": "MX", + "swift_code_bic": "123456789", + "swift_account_holder_name": "John Doe", + "swift_account_number_iban": "123456789", + "transfers_account": "BM123123123123", + "transfers_type": "CVU", + "has_virtual_account": True, + }, + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_export_payouts, "error": None} + + response = await self.blindpay.payouts.export() + + assert response["error"] is None + assert response["data"] == mocked_export_payouts + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/export/payouts") + + @pytest.mark.asyncio + async def test_get_payout_track(self): + mocked_payout_track = { + "receiver_id": "re_000000000000", + "id": "pa_000000000000", + "status": "processing", + "sender_wallet_address": "0x123...890", + "signed_transaction": "AAA...Zey8y0A", + "quote_id": "qu_000000000000", + "instance_id": "in_000000000000", + "tracking_transaction": { + "step": "processing", + "status": "failed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_payment": { + "step": "on_hold", + "provider_name": "blockchain", + "provider_transaction_id": "0x123...890", + "provider_status": "canceled", + "estimated_time_of_arrival": "5_min", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_liquidity": { + "step": "processing", + "provider_transaction_id": "0x123...890", + "provider_status": "deposited", + "estimated_time_of_arrival": "1_business_day", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_complete": { + "step": "on_hold", + "status": "tokens_refunded", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_partner_fee": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + "image_url": "https://example.com/image.png", + "first_name": "John", + "last_name": "Doe", + "legal_name": "Company Name Inc.", + "network": "sepolia", + "token": "USDC", + "description": "Memo code or description, only works with USD and BRL", + "sender_amount": 1010, + "receiver_amount": 5240, + "partner_fee_amount": 150, + "commercial_quotation": 495, + "blindpay_quotation": 485, + "total_fee_amount": 1.5, + "receiver_local_amount": 1000, + "currency": "BRL", + "transaction_document_file": "https://example.com/image.png", + "transaction_document_type": "invoice", + "transaction_document_id": "1234567890", + "name": "Bank Account Name", + "type": "wire", + "pix_key": "14947677768", + "account_number": "1001001234", + "routing_number": "012345678", + "country": "US", + "account_class": "individual", + "address_line_1": "Address line 1", + "address_line_2": "Address line 2", + "city": "City", + "state_province_region": "State/Province/Region", + "postal_code": "Postal code", + "account_type": "checking", + "ach_cop_beneficiary_first_name": "Fernando", + "ach_cop_bank_account": "12345678", + "ach_cop_bank_code": "051", + "ach_cop_beneficiary_last_name": "Guzman Alarcón", + "ach_cop_document_id": "1661105408", + "ach_cop_document_type": "CC", + "ach_cop_email": "fernando.guzman@gmail.com", + "beneficiary_name": "Individual full name or business name", + "spei_clabe": "5482347403740546", + "spei_protocol": "clabe", + "spei_institution_code": "40002", + "swift_beneficiary_country": "MX", + "swift_code_bic": "123456789", + "swift_account_holder_name": "John Doe", + "swift_account_number_iban": "123456789", + "transfers_account": "BM123123123123", + "transfers_type": "CVU", + "has_virtual_account": True, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_payout_track, "error": None} + + response = await self.blindpay.payouts.get_track("pa_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_payout_track + mock_request.assert_called_once_with("GET", "/e/payouts/pa_000000000000") + + @pytest.mark.asyncio + async def test_authorize_stellar_token(self): + mocked_authorize_token = { + "transaction_hash": "string", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_authorize_token, "error": None} + + response = await self.blindpay.payouts.authorize_stellar_token( + { + "quote_id": "qu_000000000000", + "sender_wallet_address": "0x123...890", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_authorize_token + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/payouts/stellar/authorize", + { + "quote_id": "qu_000000000000", + "sender_wallet_address": "0x123...890", + }, + ) + + @pytest.mark.asyncio + async def test_create_stellar_payout(self): + mocked_stellar_payout = { + "id": "pa_000000000000", + "status": "processing", + "sender_wallet_address": "0x123...890", + "tracking_complete": { + "step": "on_hold", + "status": "tokens_refunded", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_payment": { + "step": "on_hold", + "provider_name": "blockchain", + "provider_transaction_id": "0x123...890", + "provider_status": "canceled", + "estimated_time_of_arrival": "5_min", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_transaction": { + "step": "processing", + "status": "failed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_partner_fee": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_liquidity": { + "step": "processing", + "provider_transaction_id": "0x123...890", + "provider_status": "deposited", + "estimated_time_of_arrival": "1_business_day", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "receiver_id": "re_000000000000", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_stellar_payout, "error": None} + + response = await self.blindpay.payouts.create_stellar( + { + "quote_id": "qu_000000000000", + "sender_wallet_address": "0x123...890", + "signed_transaction": "signed_xdr_string", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_stellar_payout + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/payouts/stellar", + { + "quote_id": "qu_000000000000", + "sender_wallet_address": "0x123...890", + "signed_transaction": "signed_xdr_string", + }, + ) + + @pytest.mark.asyncio + async def test_create_evm_payout(self): + mocked_evm_payout = { + "id": "pa_000000000000", + "status": "processing", + "sender_wallet_address": "0x123...890", + "tracking_complete": { + "step": "on_hold", + "status": "tokens_refunded", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_payment": { + "step": "on_hold", + "provider_name": "blockchain", + "provider_transaction_id": "0x123...890", + "provider_status": "canceled", + "estimated_time_of_arrival": "5_min", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_transaction": { + "step": "processing", + "status": "failed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_partner_fee": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_liquidity": { + "step": "processing", + "provider_transaction_id": "0x123...890", + "provider_status": "deposited", + "estimated_time_of_arrival": "1_business_day", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "receiver_id": "re_000000000000", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_evm_payout, "error": None} + + response = await self.blindpay.payouts.create_evm( + { + "quote_id": "qu_000000000000", + "sender_wallet_address": "0x123...890", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_evm_payout + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/payouts/evm", + { + "quote_id": "qu_000000000000", + "sender_wallet_address": "0x123...890", + }, + ) + + +class TestPayoutsSync: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPaySync(api_key="test-key", instance_id="in_000000000000") + + def test_list_payouts(self): + mocked_payouts = { + "data": [ + { + "receiver_id": "re_000000000000", + "id": "pa_000000000000", + "status": "processing", + "sender_wallet_address": "0x123...890", + "signed_transaction": "AAA...Zey8y0A", + "quote_id": "qu_000000000000", + "instance_id": "in_000000000000", + "tracking_transaction": { + "step": "processing", + "status": "failed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_payment": { + "step": "on_hold", + "provider_name": "blockchain", + "provider_transaction_id": "0x123...890", + "provider_status": "canceled", + "estimated_time_of_arrival": "5_min", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_liquidity": { + "step": "processing", + "provider_transaction_id": "0x123...890", + "provider_status": "deposited", + "estimated_time_of_arrival": "1_business_day", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_complete": { + "step": "on_hold", + "status": "tokens_refunded", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_partner_fee": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + "image_url": "https://example.com/image.png", + "first_name": "John", + "last_name": "Doe", + "legal_name": "Company Name Inc.", + "network": "sepolia", + "token": "USDC", + "description": "Memo code or description, only works with USD and BRL", + "sender_amount": 1010, + "receiver_amount": 5240, + "partner_fee_amount": 150, + "commercial_quotation": 495, + "blindpay_quotation": 485, + "total_fee_amount": 1.5, + "receiver_local_amount": 1000, + "currency": "BRL", + "transaction_document_file": "https://example.com/image.png", + "transaction_document_type": "invoice", + "transaction_document_id": "1234567890", + "name": "Bank Account Name", + "type": "wire", + "pix_key": "14947677768", + "account_number": "1001001234", + "routing_number": "012345678", + "country": "US", + "account_class": "individual", + "address_line_1": "Address line 1", + "address_line_2": "Address line 2", + "city": "City", + "state_province_region": "State/Province/Region", + "postal_code": "Postal code", + "account_type": "checking", + "ach_cop_beneficiary_first_name": "Fernando", + "ach_cop_bank_account": "12345678", + "ach_cop_bank_code": "051", + "ach_cop_beneficiary_last_name": "Guzman Alarcón", + "ach_cop_document_id": "1661105408", + "ach_cop_document_type": "CC", + "ach_cop_email": "fernando.guzman@gmail.com", + "beneficiary_name": "Individual full name or business name", + "spei_clabe": "5482347403740546", + "spei_protocol": "clabe", + "spei_institution_code": "40002", + "swift_beneficiary_country": "MX", + "swift_code_bic": "123456789", + "swift_account_holder_name": "John Doe", + "swift_account_number_iban": "123456789", + "transfers_account": "BM123123123123", + "transfers_type": "CVU", + "has_virtual_account": True, + }, + ], + "pagination": { + "has_more": True, + "next_page": 3, + "prev_page": 1, + }, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_payouts, "error": None} + + response = self.blindpay.payouts.list() + + assert response["error"] is None + assert response["data"] == mocked_payouts + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/payouts") + + def test_get_payout(self): + mocked_payout = { + "receiver_id": "re_000000000000", + "id": "pa_000000000000", + "status": "processing", + "sender_wallet_address": "0x123...890", + "signed_transaction": "AAA...Zey8y0A", + "quote_id": "qu_000000000000", + "instance_id": "in_000000000000", + "tracking_transaction": { + "step": "processing", + "status": "failed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_payment": { + "step": "on_hold", + "provider_name": "blockchain", + "provider_transaction_id": "0x123...890", + "provider_status": "canceled", + "estimated_time_of_arrival": "5_min", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_liquidity": { + "step": "processing", + "provider_transaction_id": "0x123...890", + "provider_status": "deposited", + "estimated_time_of_arrival": "1_business_day", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_complete": { + "step": "on_hold", + "status": "tokens_refunded", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_partner_fee": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + "image_url": "https://example.com/image.png", + "first_name": "John", + "last_name": "Doe", + "legal_name": "Company Name Inc.", + "network": "sepolia", + "token": "USDC", + "description": "Memo code or description, only works with USD and BRL", + "sender_amount": 1010, + "receiver_amount": 5240, + "partner_fee_amount": 150, + "commercial_quotation": 495, + "blindpay_quotation": 485, + "total_fee_amount": 1.5, + "receiver_local_amount": 1000, + "currency": "BRL", + "transaction_document_file": "https://example.com/image.png", + "transaction_document_type": "invoice", + "transaction_document_id": "1234567890", + "name": "Bank Account Name", + "type": "wire", + "pix_key": "14947677768", + "account_number": "1001001234", + "routing_number": "012345678", + "country": "US", + "account_class": "individual", + "address_line_1": "Address line 1", + "address_line_2": "Address line 2", + "city": "City", + "state_province_region": "State/Province/Region", + "postal_code": "Postal code", + "account_type": "checking", + "ach_cop_beneficiary_first_name": "Fernando", + "ach_cop_bank_account": "12345678", + "ach_cop_bank_code": "051", + "ach_cop_beneficiary_last_name": "Guzman Alarcón", + "ach_cop_document_id": "1661105408", + "ach_cop_document_type": "CC", + "ach_cop_email": "fernando.guzman@gmail.com", + "beneficiary_name": "Individual full name or business name", + "spei_clabe": "5482347403740546", + "spei_protocol": "clabe", + "spei_institution_code": "40002", + "swift_beneficiary_country": "MX", + "swift_code_bic": "123456789", + "swift_account_holder_name": "John Doe", + "swift_account_number_iban": "123456789", + "transfers_account": "BM123123123123", + "transfers_type": "CVU", + "has_virtual_account": True, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_payout, "error": None} + + response = self.blindpay.payouts.get("pa_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_payout + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/payouts/pa_000000000000") + + def test_export_payouts(self): + mocked_export_payouts = [ + { + "receiver_id": "re_000000000000", + "id": "pa_000000000000", + "status": "processing", + "sender_wallet_address": "0x123...890", + "signed_transaction": "AAA...Zey8y0A", + "quote_id": "qu_000000000000", + "instance_id": "in_000000000000", + "tracking_transaction": { + "step": "processing", + "status": "failed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_payment": { + "step": "on_hold", + "provider_name": "blockchain", + "provider_transaction_id": "0x123...890", + "provider_status": "canceled", + "estimated_time_of_arrival": "5_min", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_liquidity": { + "step": "processing", + "provider_transaction_id": "0x123...890", + "provider_status": "deposited", + "estimated_time_of_arrival": "1_business_day", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_complete": { + "step": "on_hold", + "status": "tokens_refunded", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_partner_fee": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + "image_url": "https://example.com/image.png", + "first_name": "John", + "last_name": "Doe", + "legal_name": "Company Name Inc.", + "network": "sepolia", + "token": "USDC", + "description": "Memo code or description, only works with USD and BRL", + "sender_amount": 1010, + "receiver_amount": 5240, + "partner_fee_amount": 150, + "commercial_quotation": 495, + "blindpay_quotation": 485, + "total_fee_amount": 1.5, + "receiver_local_amount": 1000, + "currency": "BRL", + "transaction_document_file": "https://example.com/image.png", + "transaction_document_type": "invoice", + "transaction_document_id": "1234567890", + "name": "Bank Account Name", + "type": "wire", + "pix_key": "14947677768", + "account_number": "1001001234", + "routing_number": "012345678", + "country": "US", + "account_class": "individual", + "address_line_1": "Address line 1", + "address_line_2": "Address line 2", + "city": "City", + "state_province_region": "State/Province/Region", + "postal_code": "Postal code", + "account_type": "checking", + "ach_cop_beneficiary_first_name": "Fernando", + "ach_cop_bank_account": "12345678", + "ach_cop_bank_code": "051", + "ach_cop_beneficiary_last_name": "Guzman Alarcón", + "ach_cop_document_id": "1661105408", + "ach_cop_document_type": "CC", + "ach_cop_email": "fernando.guzman@gmail.com", + "beneficiary_name": "Individual full name or business name", + "spei_clabe": "5482347403740546", + "spei_protocol": "clabe", + "spei_institution_code": "40002", + "swift_beneficiary_country": "MX", + "swift_code_bic": "123456789", + "swift_account_holder_name": "John Doe", + "swift_account_number_iban": "123456789", + "transfers_account": "BM123123123123", + "transfers_type": "CVU", + "has_virtual_account": True, + }, + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_export_payouts, "error": None} + + response = self.blindpay.payouts.export() + + assert response["error"] is None + assert response["data"] == mocked_export_payouts + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/export/payouts") + + def test_get_payout_track(self): + mocked_payout_track = { + "receiver_id": "re_000000000000", + "id": "pa_000000000000", + "status": "processing", + "sender_wallet_address": "0x123...890", + "signed_transaction": "AAA...Zey8y0A", + "quote_id": "qu_000000000000", + "instance_id": "in_000000000000", + "tracking_transaction": { + "step": "processing", + "status": "failed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_payment": { + "step": "on_hold", + "provider_name": "blockchain", + "provider_transaction_id": "0x123...890", + "provider_status": "canceled", + "estimated_time_of_arrival": "5_min", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_liquidity": { + "step": "processing", + "provider_transaction_id": "0x123...890", + "provider_status": "deposited", + "estimated_time_of_arrival": "1_business_day", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_complete": { + "step": "on_hold", + "status": "tokens_refunded", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_partner_fee": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + "image_url": "https://example.com/image.png", + "first_name": "John", + "last_name": "Doe", + "legal_name": "Company Name Inc.", + "network": "sepolia", + "token": "USDC", + "description": "Memo code or description, only works with USD and BRL", + "sender_amount": 1010, + "receiver_amount": 5240, + "partner_fee_amount": 150, + "commercial_quotation": 495, + "blindpay_quotation": 485, + "total_fee_amount": 1.5, + "receiver_local_amount": 1000, + "currency": "BRL", + "transaction_document_file": "https://example.com/image.png", + "transaction_document_type": "invoice", + "transaction_document_id": "1234567890", + "name": "Bank Account Name", + "type": "wire", + "pix_key": "14947677768", + "account_number": "1001001234", + "routing_number": "012345678", + "country": "US", + "account_class": "individual", + "address_line_1": "Address line 1", + "address_line_2": "Address line 2", + "city": "City", + "state_province_region": "State/Province/Region", + "postal_code": "Postal code", + "account_type": "checking", + "ach_cop_beneficiary_first_name": "Fernando", + "ach_cop_bank_account": "12345678", + "ach_cop_bank_code": "051", + "ach_cop_beneficiary_last_name": "Guzman Alarcón", + "ach_cop_document_id": "1661105408", + "ach_cop_document_type": "CC", + "ach_cop_email": "fernando.guzman@gmail.com", + "beneficiary_name": "Individual full name or business name", + "spei_clabe": "5482347403740546", + "spei_protocol": "clabe", + "spei_institution_code": "40002", + "swift_beneficiary_country": "MX", + "swift_code_bic": "123456789", + "swift_account_holder_name": "John Doe", + "swift_account_number_iban": "123456789", + "transfers_account": "BM123123123123", + "transfers_type": "CVU", + "has_virtual_account": True, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_payout_track, "error": None} + + response = self.blindpay.payouts.get_track("pa_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_payout_track + mock_request.assert_called_once_with("GET", "/e/payouts/pa_000000000000") + + def test_authorize_stellar_token(self): + mocked_authorize_token = { + "transaction_hash": "string", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_authorize_token, "error": None} + + response = self.blindpay.payouts.authorize_stellar_token( + { + "quote_id": "qu_000000000000", + "sender_wallet_address": "0x123...890", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_authorize_token + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/payouts/stellar/authorize", + { + "quote_id": "qu_000000000000", + "sender_wallet_address": "0x123...890", + }, + ) + + def test_create_stellar_payout(self): + mocked_stellar_payout = { + "id": "pa_000000000000", + "status": "processing", + "sender_wallet_address": "0x123...890", + "tracking_complete": { + "step": "on_hold", + "status": "tokens_refunded", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_payment": { + "step": "on_hold", + "provider_name": "blockchain", + "provider_transaction_id": "0x123...890", + "provider_status": "canceled", + "estimated_time_of_arrival": "5_min", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_transaction": { + "step": "processing", + "status": "failed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_partner_fee": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_liquidity": { + "step": "processing", + "provider_transaction_id": "0x123...890", + "provider_status": "deposited", + "estimated_time_of_arrival": "1_business_day", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "receiver_id": "re_000000000000", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_stellar_payout, "error": None} + + response = self.blindpay.payouts.create_stellar( + { + "quote_id": "qu_000000000000", + "sender_wallet_address": "0x123...890", + "signed_transaction": "signed_xdr_string", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_stellar_payout + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/payouts/stellar", + { + "quote_id": "qu_000000000000", + "sender_wallet_address": "0x123...890", + "signed_transaction": "signed_xdr_string", + }, + ) + + def test_create_evm_payout(self): + mocked_evm_payout = { + "id": "pa_000000000000", + "status": "processing", + "sender_wallet_address": "0x123...890", + "tracking_complete": { + "step": "on_hold", + "status": "tokens_refunded", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_payment": { + "step": "on_hold", + "provider_name": "blockchain", + "provider_transaction_id": "0x123...890", + "provider_status": "canceled", + "estimated_time_of_arrival": "5_min", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_transaction": { + "step": "processing", + "status": "failed", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_partner_fee": { + "step": "on_hold", + "transaction_hash": "0x123...890", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "tracking_liquidity": { + "step": "processing", + "provider_transaction_id": "0x123...890", + "provider_status": "deposited", + "estimated_time_of_arrival": "1_business_day", + "completed_at": "2011-10-05T14:48:00.000Z", + }, + "receiver_id": "re_000000000000", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_evm_payout, "error": None} + + response = self.blindpay.payouts.create_evm( + { + "quote_id": "qu_000000000000", + "sender_wallet_address": "0x123...890", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_evm_payout + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/payouts/evm", + { + "quote_id": "qu_000000000000", + "sender_wallet_address": "0x123...890", + }, + ) diff --git a/tests/resources/test_quotes.py b/tests/resources/test_quotes.py new file mode 100644 index 0000000..9e8ed63 --- /dev/null +++ b/tests/resources/test_quotes.py @@ -0,0 +1,216 @@ +from unittest.mock import patch + +import pytest + +from blindpay import BlindPay, BlindPaySync +from blindpay.resources.quotes.quotes import CreateQuoteResponse + + +class TestQuotes: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPay(api_key="test-key", instance_id="in_000000000000") + + @pytest.mark.asyncio + async def test_create_quote(self): + mocked_quote: CreateQuoteResponse = { + "id": "qu_000000000000", + "expires_at": 1712958191, + "commercial_quotation": 495, + "blindpay_quotation": 485, + "receiver_amount": 5240, + "sender_amount": 1010, + "partner_fee_amount": 150, + "flat_fee": 50, + "contract": { + "abi": [{}], + "address": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", + "functionName": "approve", + "blindpayContractAddress": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", + "amount": "1000000000000000000", + "network": { + "name": "Ethereum", + "chainId": 1, + }, + }, + "receiver_local_amount": 1000, + "description": "Memo code or description, only works with USD and BRL", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_quote, "error": None} + + response = await self.blindpay.quotes.create( + { + "bank_account_id": "ba_000000000000", + "currency_type": "sender", + "network": "sepolia", + "request_amount": 1000, + "token": "USDC", + "cover_fees": True, + "description": "Memo code or description, only works with USD and BRL", + "partner_fee_id": "pf_000000000000", + "transaction_document_file": None, + "transaction_document_id": None, + "transaction_document_type": "invoice", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_quote + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/quotes", + { + "bank_account_id": "ba_000000000000", + "currency_type": "sender", + "network": "sepolia", + "request_amount": 1000, + "token": "USDC", + "cover_fees": True, + "description": "Memo code or description, only works with USD and BRL", + "partner_fee_id": "pf_000000000000", + "transaction_document_file": None, + "transaction_document_id": None, + "transaction_document_type": "invoice", + }, + ) + + @pytest.mark.asyncio + async def test_get_fx_rate(self): + mocked_fx_rate = { + "commercial_quotation": 495, + "blindpay_quotation": 485, + "result_amount": 1, + "instance_flat_fee": 50, + "instance_percentage_fee": 0, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_fx_rate, "error": None} + + response = await self.blindpay.quotes.get_fx_rate( + { + "currency_type": "sender", + "from_currency": "USD", + "to": "BRL", + "request_amount": 1000, + } + ) + + assert response["error"] is None + assert response["data"] == mocked_fx_rate + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/quotes/fx", + { + "currency_type": "sender", + "from": "USD", + "to": "BRL", + "request_amount": 1000, + }, + ) + + +class TestQuotesSync: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPaySync(api_key="test-key", instance_id="in_000000000000") + + def test_create_quote(self): + mocked_quote: CreateQuoteResponse = { + "id": "qu_000000000000", + "expires_at": 1712958191, + "commercial_quotation": 495, + "blindpay_quotation": 485, + "receiver_amount": 5240, + "sender_amount": 1010, + "partner_fee_amount": 150, + "flat_fee": 50, + "contract": { + "abi": [{}], + "address": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", + "functionName": "approve", + "blindpayContractAddress": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", + "amount": "1000000000000000000", + "network": { + "name": "Ethereum", + "chainId": 1, + }, + }, + "receiver_local_amount": 1000, + "description": "Memo code or description, only works with USD and BRL", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_quote, "error": None} + + response = self.blindpay.quotes.create( + { + "bank_account_id": "ba_000000000000", + "currency_type": "sender", + "network": "sepolia", + "request_amount": 1000, + "token": "USDC", + "cover_fees": True, + "description": "Memo code or description, only works with USD and BRL", + "partner_fee_id": "pf_000000000000", + "transaction_document_file": None, + "transaction_document_id": None, + "transaction_document_type": "invoice", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_quote + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/quotes", + { + "bank_account_id": "ba_000000000000", + "currency_type": "sender", + "network": "sepolia", + "request_amount": 1000, + "token": "USDC", + "cover_fees": True, + "description": "Memo code or description, only works with USD and BRL", + "partner_fee_id": "pf_000000000000", + "transaction_document_file": None, + "transaction_document_id": None, + "transaction_document_type": "invoice", + }, + ) + + def test_get_fx_rate(self): + mocked_fx_rate = { + "commercial_quotation": 495, + "blindpay_quotation": 485, + "result_amount": 1, + "instance_flat_fee": 50, + "instance_percentage_fee": 0, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_fx_rate, "error": None} + + response = self.blindpay.quotes.get_fx_rate( + { + "currency_type": "sender", + "from_currency": "USD", + "to": "BRL", + "request_amount": 1000, + } + ) + + assert response["error"] is None + assert response["data"] == mocked_fx_rate + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/quotes/fx", + { + "currency_type": "sender", + "from": "USD", + "to": "BRL", + "request_amount": 1000, + }, + ) diff --git a/tests/resources/test_receivers.py b/tests/resources/test_receivers.py new file mode 100644 index 0000000..d5629c9 --- /dev/null +++ b/tests/resources/test_receivers.py @@ -0,0 +1,1071 @@ +from unittest.mock import patch + +import pytest + +from blindpay import BlindPay, BlindPaySync + + +class TestReceivers: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPay(api_key="test-key", instance_id="in_000000000000") + + @pytest.mark.asyncio + async def test_list_receivers(self): + mocked_receivers = [ + { + "id": "re_Euw7HN4OdxPn", + "type": "individual", + "kyc_type": "standard", + "kyc_status": "verifying", + "kyc_warnings": [ + { + "code": None, + "message": None, + "resolution_status": None, + "warning_id": None, + }, + ], + "email": "bernardo@gmail.com", + "tax_id": "12345678900", + "address_line_1": "Av. Paulista, 1000", + "address_line_2": "Apto 101", + "city": "São Paulo", + "state_province_region": "SP", + "country": "BR", + "postal_code": "01310-100", + "ip_address": "127.0.0.1", + "image_url": "https://example.com/image.png", + "phone_number": "+5511987654321", + "proof_of_address_doc_type": "UTILITY_BILL", + "proof_of_address_doc_file": "https://example.com/image.png", + "first_name": "Bernardo", + "last_name": "Simonassi", + "date_of_birth": "1998-02-02T00:00:00.000Z", + "id_doc_country": "BR", + "id_doc_type": "PASSPORT", + "id_doc_front_file": "https://example.com/image.png", + "id_doc_back_file": "https://example.com/image.png", + "aiprise_validation_key": "", + "instance_id": "in_000000000000", + "tos_id": "to_3ZZhllJkvo5Z", + "created_at": "2021-01-01T00:00:00.000Z", + "updated_at": "2021-01-01T00:00:00.000Z", + "limit": { + "per_transaction": 100000, + "daily": 200000, + "monthly": 1000000, + }, + }, + { + "id": "re_YuaMcI2B8zbQ", + "type": "individual", + "kyc_type": "enhanced", + "kyc_status": "approved", + "kyc_warnings": None, + "email": "alice.johnson@example.com", + "tax_id": "98765432100", + "address_line_1": "123 Main St", + "address_line_2": None, + "city": "New York", + "state_province_region": "NY", + "country": "US", + "postal_code": "10001", + "ip_address": "192.168.1.1", + "image_url": None, + "phone_number": "+15555555555", + "proof_of_address_doc_type": "BANK_STATEMENT", + "proof_of_address_doc_file": "https://example.com/image.png", + "first_name": "Alice", + "last_name": "Johnson", + "date_of_birth": "1990-05-10T00:00:00.000Z", + "id_doc_country": "US", + "id_doc_type": "PASSPORT", + "id_doc_front_file": "https://example.com/image.png", + "id_doc_back_file": None, + "aiprise_validation_key": "enhanced-key", + "instance_id": "in_000000000001", + "source_of_funds_doc_type": "salary", + "source_of_funds_doc_file": "https://example.com/image.png", + "individual_holding_doc_front_file": "https://example.com/image.png", + "purpose_of_transactions": "investment_purposes", + "purpose_of_transactions_explanation": "Investing in stocks", + "tos_id": "to_nppX66ntvtHs", + "created_at": "2022-02-02T00:00:00.000Z", + "updated_at": "2022-02-02T00:00:00.000Z", + "limit": { + "per_transaction": 50000, + "daily": 100000, + "monthly": 500000, + }, + }, + { + "id": "re_IOxAUL24LG7P", + "type": "business", + "kyc_type": "standard", + "kyc_status": "pending", + "kyc_warnings": None, + "email": "business@example.com", + "tax_id": "20096178000195", + "address_line_1": "1 King St W", + "address_line_2": "Suite 100", + "city": "Toronto", + "state_province_region": "ON", + "country": "CA", + "postal_code": "M5H 1A1", + "ip_address": None, + "image_url": None, + "phone_number": "+14165555555", + "proof_of_address_doc_type": "UTILITY_BILL", + "proof_of_address_doc_file": "https://example.com/image.png", + "legal_name": "Business Corp", + "alternate_name": "BizCo", + "formation_date": "2010-01-01T00:00:00.000Z", + "website": "https://businesscorp.com", + "owners": [ + { + "role": "beneficial_owner", + "first_name": "Carlos", + "last_name": "Silva", + "date_of_birth": "1995-05-15T00:00:00.000Z", + "tax_id": "12345678901", + "address_line_1": "Rua Augusta, 1500", + "address_line_2": None, + "city": "São Paulo", + "state_province_region": "SP", + "country": "BR", + "postal_code": "01304-001", + "id_doc_country": "BR", + "id_doc_type": "PASSPORT", + "id_doc_front_file": "https://example.com/image.png", + "id_doc_back_file": "https://example.com/image.png", + "proof_of_address_doc_type": "UTILITY_BILL", + "proof_of_address_doc_file": "https://example.com/image.png", + "id": "ub_000000000000", + "instance_id": "in_000000000000", + "receiver_id": "re_IOxAUL24LG7P", + }, + ], + "incorporation_doc_file": "https://example.com/image.png", + "proof_of_ownership_doc_file": "https://example.com/image.png", + "external_id": None, + "instance_id": "in_000000000002", + "tos_id": "to_nppX66ntvtHs", + "aiprise_validation_key": "", + "created_at": "2015-03-15T00:00:00.000Z", + "updated_at": "2015-03-15T00:00:00.000Z", + "limit": { + "per_transaction": 200000, + "daily": 400000, + "monthly": 2000000, + }, + }, + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_receivers, "error": None} + + response = await self.blindpay.receivers.list() + + assert response["error"] is None + assert response["data"] == mocked_receivers + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/receivers") + + @pytest.mark.asyncio + async def test_create_individual_with_standard_kyc(self): + mocked_receiver = { + "id": "re_Euw7HN4OdxPn", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_receiver, "error": None} + + response = await self.blindpay.receivers.create_individual_with_standard_kyc( + { + "email": "bernardo.simonassi@gmail.com", + "tax_id": "12345678900", + "address_line_1": "Av. Paulista, 1000", + "address_line_2": "Apto 101", + "city": "São Paulo", + "state_province_region": "SP", + "country": "BR", + "postal_code": "01310-100", + "phone_number": "+5511987654321", + "first_name": "Bernardo", + "last_name": "Simonassi", + "date_of_birth": "1998-02-02T00:00:00.000Z", + "id_doc_country": "BR", + "id_doc_type": "PASSPORT", + "id_doc_front_file": "https://example.com/image.png", + "id_doc_back_file": "https://example.com/image.png", + "proof_of_address_doc_type": "UTILITY_BILL", + "proof_of_address_doc_file": "https://example.com/image.png", + "tos_id": "to_tPiz4bM2nh5K", + } + ) + + assert response["error"] is None + assert response["data"] == {"id": "re_Euw7HN4OdxPn"} + + @pytest.mark.asyncio + async def test_create_individual_with_enhanced_kyc(self): + mocked_receiver = { + "id": "re_YuaMcI2B8zbQ", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_receiver, "error": None} + + response = await self.blindpay.receivers.create_individual_with_enhanced_kyc( + { + "email": "bernardo.simonassi@gmail.com", + "tax_id": "12345678900", + "address_line_1": "Av. Paulista, 1000", + "address_line_2": "Apto 101", + "city": "São Paulo", + "state_province_region": "SP", + "country": "BR", + "postal_code": "01310-100", + "first_name": "Bernardo", + "last_name": "Simonassi", + "phone_number": "+5511987654321", + "date_of_birth": "1998-02-02T00:00:00.000Z", + "id_doc_country": "BR", + "id_doc_type": "PASSPORT", + "id_doc_front_file": "https://example.com/image.png", + "id_doc_back_file": "https://example.com/image.png", + "proof_of_address_doc_type": "UTILITY_BILL", + "proof_of_address_doc_file": "https://example.com/image.png", + "individual_holding_doc_front_file": "https://example.com/image.png", + "purpose_of_transactions": "personal_or_living_expenses", + "source_of_funds_doc_type": "savings", + "purpose_of_transactions_explanation": "I am receiving salary payments from my employer", + "source_of_funds_doc_file": "https://example.com/image.png", + "tos_id": "to_3ZZhllJkvo5Z", + } + ) + + assert response["error"] is None + assert response["data"] == {"id": "re_YuaMcI2B8zbQ"} + + @pytest.mark.asyncio + async def test_create_business_with_standard_kyb(self): + mocked_receiver = { + "id": "re_IOxAUL24LG7P", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_receiver, "error": None} + + response = await self.blindpay.receivers.create_business_with_standard_kyb( + { + "email": "contato@empresa.com.br", + "tax_id": "20096178000195", + "address_line_1": "Av. Brigadeiro Faria Lima, 400", + "address_line_2": "Sala 1201", + "city": "São Paulo", + "state_province_region": "SP", + "country": "BR", + "postal_code": "04538-132", + "legal_name": "Empresa Exemplo Ltda", + "alternate_name": "Exemplo", + "formation_date": "2010-05-20T00:00:00.000Z", + "incorporation_doc_file": "https://example.com/image.png", + "proof_of_address_doc_type": "UTILITY_BILL", + "proof_of_address_doc_file": "https://example.com/image.png", + "proof_of_ownership_doc_file": "https://example.com/image.png", + "tos_id": "to_nppX66ntvtHs", + "website": "https://site.com/", + "owners": [ + { + "role": "beneficial_owner", + "first_name": "Carlos", + "last_name": "Silva", + "date_of_birth": "1995-05-15T00:00:00.000Z", + "tax_id": "12345678901", + "address_line_1": "Rua Augusta, 1500", + "address_line_2": None, + "city": "São Paulo", + "state_province_region": "SP", + "country": "BR", + "postal_code": "01304-001", + "id_doc_country": "BR", + "id_doc_type": "PASSPORT", + "id_doc_front_file": "https://example.com/image.png", + "id_doc_back_file": "https://example.com/image.png", + "proof_of_address_doc_type": "UTILITY_BILL", + "proof_of_address_doc_file": "https://example.com/image.png", + "id": "ub_000000000000", + "instance_id": "in_000000000000", + "receiver_id": "re_IOxAUL24LG7P", + }, + ], + } + ) + + assert response["error"] is None + assert response["data"] == {"id": "re_IOxAUL24LG7P"} + + @pytest.mark.asyncio + async def test_get_receiver(self): + mocked_receiver = { + "id": "re_YuaMcI2B8zbQ", + "type": "individual", + "kyc_type": "enhanced", + "kyc_status": "verifying", + "kyc_warnings": [ + { + "code": None, + "message": None, + "resolution_status": None, + "warning_id": None, + }, + ], + "email": "bernardo.simonassi@gmail.com", + "tax_id": "12345678900", + "address_line_1": "Av. Paulista, 1000", + "address_line_2": "Apto 101", + "city": "São Paulo", + "state_province_region": "SP", + "country": "BR", + "postal_code": "01310-100", + "ip_address": "127.0.0.1", + "image_url": "https://example.com/image.png", + "phone_number": "+5511987654321", + "proof_of_address_doc_type": "UTILITY_BILL", + "proof_of_address_doc_file": "https://example.com/image.png", + "first_name": "Bernardo", + "last_name": "Simonassi", + "date_of_birth": "1998-02-02T00:00:00.000Z", + "id_doc_country": "BR", + "id_doc_type": "PASSPORT", + "id_doc_front_file": "https://example.com/image.png", + "id_doc_back_file": "https://example.com/image.png", + "aiprise_validation_key": "", + "source_of_funds_doc_type": "savings", + "source_of_funds_doc_file": "https://example.com/image.png", + "individual_holding_doc_front_file": "https://example.com/image.png", + "purpose_of_transactions": "personal_or_living_expenses", + "purpose_of_transactions_explanation": "I am receiving salary payments from my employer", + "instance_id": "in_000000000000", + "tos_id": "to_3ZZhllJkvo5Z", + "created_at": "2021-01-01T00:00:00.000Z", + "updated_at": "2021-01-01T00:00:00.000Z", + "limit": { + "per_transaction": 100000, + "daily": 200000, + "monthly": 1000000, + }, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_receiver, "error": None} + + response = await self.blindpay.receivers.get("re_YuaMcI2B8zbQ") + + assert response["error"] is None + assert response["data"] == mocked_receiver + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/receivers/re_YuaMcI2B8zbQ") + + @pytest.mark.asyncio + async def test_update_receiver(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = await self.blindpay.receivers.update( + { + "receiver_id": "re_YuaMcI2B8zbQ", + "email": "bernardo.simonassi@gmail.com", + "tax_id": "12345678900", + "address_line_1": "Av. Paulista, 1000", + "address_line_2": "Apto 101", + "city": "São Paulo", + "state_province_region": "SP", + "country": "BR", + "postal_code": "01310-100", + "ip_address": "127.0.0.1", + "image_url": "https://example.com/image.png", + "phone_number": "+5511987654321", + "proof_of_address_doc_type": "UTILITY_BILL", + "proof_of_address_doc_file": "https://example.com/image.png", + "first_name": "Bernardo", + "last_name": "Simonassi", + "date_of_birth": "1998-02-02T00:00:00.000Z", + "id_doc_country": "BR", + "id_doc_type": "PASSPORT", + "id_doc_front_file": "https://example.com/image.png", + "id_doc_back_file": "https://example.com/image.png", + "alternate_name": "Exemplo", + "formation_date": "2010-05-20T00:00:00.000Z", + "website": "https://site.com", + "owners": [ + { + "id": "ub_000000000000", + "first_name": "Carlos", + "last_name": "Silva", + "role": "beneficial_owner", + "date_of_birth": "1995-05-15T00:00:00.000Z", + "tax_id": "12345678901", + "address_line_1": "Rua Augusta, 1500", + "address_line_2": None, + "city": "São Paulo", + "state_province_region": "SP", + "country": "BR", + "postal_code": "01304-001", + "id_doc_country": "BR", + "id_doc_type": "PASSPORT", + "id_doc_front_file": "https://example.com/image.png", + "id_doc_back_file": "https://example.com/image.png", + }, + ], + "incorporation_doc_file": "https://example.com/image.png", + "proof_of_ownership_doc_file": "https://example.com/image.png", + "source_of_funds_doc_type": "savings", + "source_of_funds_doc_file": "https://example.com/image.png", + "individual_holding_doc_front_file": "https://example.com/image.png", + "purpose_of_transactions": "personal_or_living_expenses", + "purpose_of_transactions_explanation": "I am receiving salary payments from my employer", + "external_id": "some-external-id", + "tos_id": "to_3ZZhllJkvo5Z", + } + ) + + assert response["error"] is None + assert response["data"] == {"data": None} + + @pytest.mark.asyncio + async def test_delete_receiver(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = await self.blindpay.receivers.delete("re_YuaMcI2B8zbQ") + + assert response["error"] is None + assert response["data"] == {"data": None} + mock_request.assert_called_once_with("DELETE", "/instances/in_000000000000/receivers/re_YuaMcI2B8zbQ", None) + + @pytest.mark.asyncio + async def test_get_receiver_limits(self): + mocked_receiver_limits = { + "limits": { + "payin": { + "daily": 10000, + "monthly": 50000, + }, + "payout": { + "daily": 20000, + "monthly": 100000, + }, + }, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_receiver_limits, "error": None} + + response = await self.blindpay.receivers.get_limits("re_YuaMcI2B8zbQ") + + assert response["error"] is None + assert response["data"] == mocked_receiver_limits + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/limits/receivers/re_YuaMcI2B8zbQ") + + @pytest.mark.asyncio + async def test_get_limit_increase_requests(self): + mocked_limit_increase_requests = [ + { + "id": "rl_000000000000", + "receiver_id": "re_YuaMcI2B8zbQ", + "status": "in_review", + "daily": 50000, + "monthly": 250000, + "per_transaction": 25000, + "supporting_document_file": "https://example.com/bank-statement.pdf", + "supporting_document_type": "individual_bank_statement", + "created_at": "2025-01-15T10:30:00.000Z", + "updated_at": "2025-01-15T10:30:00.000Z", + }, + { + "id": "rl_000000000000", + "receiver_id": "re_YuaMcI2B8zbQ", + "status": "approved", + "daily": 30000, + "monthly": 150000, + "per_transaction": 15000, + "supporting_document_file": "https://example.com/proof-of-income.pdf", + "supporting_document_type": "individual_proof_of_income", + "created_at": "2024-12-10T14:20:00.000Z", + "updated_at": "2024-12-12T09:45:00.000Z", + }, + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_limit_increase_requests, "error": None} + + response = await self.blindpay.receivers.get_limit_increase_requests("re_YuaMcI2B8zbQ") + + assert response["error"] is None + assert response["data"] == mocked_limit_increase_requests + mock_request.assert_called_once_with( + "GET", "/instances/in_000000000000/receivers/re_YuaMcI2B8zbQ/limit-increase" + ) + + @pytest.mark.asyncio + async def test_request_limit_increase(self): + mocked_response = { + "id": "rl_000000000000", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_response, "error": None} + + response = await self.blindpay.receivers.request_limit_increase( + { + "receiver_id": "re_YuaMcI2B8zbQ", + "daily": 100000, + "monthly": 500000, + "per_transaction": 50000, + "supporting_document_file": "https://example.com/tax-return.pdf", + "supporting_document_type": "individual_tax_return", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_response + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/receivers/re_YuaMcI2B8zbQ/limit-increase", + { + "daily": 100000, + "monthly": 500000, + "per_transaction": 50000, + "supporting_document_file": "https://example.com/tax-return.pdf", + "supporting_document_type": "individual_tax_return", + }, + ) + + +class TestReceiversSync: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPaySync(api_key="test-key", instance_id="in_000000000000") + + def test_list_receivers(self): + mocked_receivers = [ + { + "id": "re_Euw7HN4OdxPn", + "type": "individual", + "kyc_type": "standard", + "kyc_status": "verifying", + "kyc_warnings": [ + { + "code": None, + "message": None, + "resolution_status": None, + "warning_id": None, + }, + ], + "email": "bernardo@gmail.com", + "tax_id": "12345678900", + "address_line_1": "Av. Paulista, 1000", + "address_line_2": "Apto 101", + "city": "São Paulo", + "state_province_region": "SP", + "country": "BR", + "postal_code": "01310-100", + "ip_address": "127.0.0.1", + "image_url": "https://example.com/image.png", + "phone_number": "+5511987654321", + "proof_of_address_doc_type": "UTILITY_BILL", + "proof_of_address_doc_file": "https://example.com/image.png", + "first_name": "Bernardo", + "last_name": "Simonassi", + "date_of_birth": "1998-02-02T00:00:00.000Z", + "id_doc_country": "BR", + "id_doc_type": "PASSPORT", + "id_doc_front_file": "https://example.com/image.png", + "id_doc_back_file": "https://example.com/image.png", + "aiprise_validation_key": "", + "instance_id": "in_000000000000", + "tos_id": "to_3ZZhllJkvo5Z", + "created_at": "2021-01-01T00:00:00.000Z", + "updated_at": "2021-01-01T00:00:00.000Z", + "limit": { + "per_transaction": 100000, + "daily": 200000, + "monthly": 1000000, + }, + }, + { + "id": "re_YuaMcI2B8zbQ", + "type": "individual", + "kyc_type": "enhanced", + "kyc_status": "approved", + "kyc_warnings": None, + "email": "alice.johnson@example.com", + "tax_id": "98765432100", + "address_line_1": "123 Main St", + "address_line_2": None, + "city": "New York", + "state_province_region": "NY", + "country": "US", + "postal_code": "10001", + "ip_address": "192.168.1.1", + "image_url": None, + "phone_number": "+15555555555", + "proof_of_address_doc_type": "BANK_STATEMENT", + "proof_of_address_doc_file": "https://example.com/image.png", + "first_name": "Alice", + "last_name": "Johnson", + "date_of_birth": "1990-05-10T00:00:00.000Z", + "id_doc_country": "US", + "id_doc_type": "PASSPORT", + "id_doc_front_file": "https://example.com/image.png", + "id_doc_back_file": None, + "aiprise_validation_key": "enhanced-key", + "instance_id": "in_000000000001", + "source_of_funds_doc_type": "salary", + "source_of_funds_doc_file": "https://example.com/image.png", + "individual_holding_doc_front_file": "https://example.com/image.png", + "purpose_of_transactions": "investment_purposes", + "purpose_of_transactions_explanation": "Investing in stocks", + "tos_id": "to_nppX66ntvtHs", + "created_at": "2022-02-02T00:00:00.000Z", + "updated_at": "2022-02-02T00:00:00.000Z", + "limit": { + "per_transaction": 50000, + "daily": 100000, + "monthly": 500000, + }, + }, + { + "id": "re_IOxAUL24LG7P", + "type": "business", + "kyc_type": "standard", + "kyc_status": "pending", + "kyc_warnings": None, + "email": "business@example.com", + "tax_id": "20096178000195", + "address_line_1": "1 King St W", + "address_line_2": "Suite 100", + "city": "Toronto", + "state_province_region": "ON", + "country": "CA", + "postal_code": "M5H 1A1", + "ip_address": None, + "image_url": None, + "phone_number": "+14165555555", + "proof_of_address_doc_type": "UTILITY_BILL", + "proof_of_address_doc_file": "https://example.com/image.png", + "legal_name": "Business Corp", + "alternate_name": "BizCo", + "formation_date": "2010-01-01T00:00:00.000Z", + "website": "https://businesscorp.com", + "owners": [ + { + "role": "beneficial_owner", + "first_name": "Carlos", + "last_name": "Silva", + "date_of_birth": "1995-05-15T00:00:00.000Z", + "tax_id": "12345678901", + "address_line_1": "Rua Augusta, 1500", + "address_line_2": None, + "city": "São Paulo", + "state_province_region": "SP", + "country": "BR", + "postal_code": "01304-001", + "id_doc_country": "BR", + "id_doc_type": "PASSPORT", + "id_doc_front_file": "https://example.com/image.png", + "id_doc_back_file": "https://example.com/image.png", + "proof_of_address_doc_type": "UTILITY_BILL", + "proof_of_address_doc_file": "https://example.com/image.png", + "id": "ub_000000000000", + "instance_id": "in_000000000000", + "receiver_id": "re_IOxAUL24LG7P", + }, + ], + "incorporation_doc_file": "https://example.com/image.png", + "proof_of_ownership_doc_file": "https://example.com/image.png", + "external_id": None, + "instance_id": "in_000000000002", + "tos_id": "to_nppX66ntvtHs", + "aiprise_validation_key": "", + "created_at": "2015-03-15T00:00:00.000Z", + "updated_at": "2015-03-15T00:00:00.000Z", + "limit": { + "per_transaction": 200000, + "daily": 400000, + "monthly": 2000000, + }, + }, + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_receivers, "error": None} + + response = self.blindpay.receivers.list() + + assert response["error"] is None + assert response["data"] == mocked_receivers + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/receivers") + + def test_create_individual_with_standard_kyc(self): + mocked_receiver = { + "id": "re_Euw7HN4OdxPn", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_receiver, "error": None} + + response = self.blindpay.receivers.create_individual_with_standard_kyc( + { + "email": "bernardo.simonassi@gmail.com", + "tax_id": "12345678900", + "address_line_1": "Av. Paulista, 1000", + "address_line_2": "Apto 101", + "city": "São Paulo", + "state_province_region": "SP", + "country": "BR", + "postal_code": "01310-100", + "phone_number": "+5511987654321", + "first_name": "Bernardo", + "last_name": "Simonassi", + "date_of_birth": "1998-02-02T00:00:00.000Z", + "id_doc_country": "BR", + "id_doc_type": "PASSPORT", + "id_doc_front_file": "https://example.com/image.png", + "id_doc_back_file": "https://example.com/image.png", + "proof_of_address_doc_type": "UTILITY_BILL", + "proof_of_address_doc_file": "https://example.com/image.png", + "tos_id": "to_tPiz4bM2nh5K", + } + ) + + assert response["error"] is None + assert response["data"] == {"id": "re_Euw7HN4OdxPn"} + + def test_create_individual_with_enhanced_kyc(self): + mocked_receiver = { + "id": "re_YuaMcI2B8zbQ", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_receiver, "error": None} + + response = self.blindpay.receivers.create_individual_with_enhanced_kyc( + { + "email": "bernardo.simonassi@gmail.com", + "tax_id": "12345678900", + "address_line_1": "Av. Paulista, 1000", + "address_line_2": "Apto 101", + "city": "São Paulo", + "state_province_region": "SP", + "country": "BR", + "postal_code": "01310-100", + "first_name": "Bernardo", + "last_name": "Simonassi", + "phone_number": "+5511987654321", + "date_of_birth": "1998-02-02T00:00:00.000Z", + "id_doc_country": "BR", + "id_doc_type": "PASSPORT", + "id_doc_front_file": "https://example.com/image.png", + "id_doc_back_file": "https://example.com/image.png", + "proof_of_address_doc_type": "UTILITY_BILL", + "proof_of_address_doc_file": "https://example.com/image.png", + "individual_holding_doc_front_file": "https://example.com/image.png", + "purpose_of_transactions": "personal_or_living_expenses", + "source_of_funds_doc_type": "savings", + "purpose_of_transactions_explanation": "I am receiving salary payments from my employer", + "source_of_funds_doc_file": "https://example.com/image.png", + "tos_id": "to_3ZZhllJkvo5Z", + } + ) + + assert response["error"] is None + assert response["data"] == {"id": "re_YuaMcI2B8zbQ"} + + def test_create_business_with_standard_kyb(self): + mocked_receiver = { + "id": "re_IOxAUL24LG7P", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_receiver, "error": None} + + response = self.blindpay.receivers.create_business_with_standard_kyb( + { + "email": "contato@empresa.com.br", + "tax_id": "20096178000195", + "address_line_1": "Av. Brigadeiro Faria Lima, 400", + "address_line_2": "Sala 1201", + "city": "São Paulo", + "state_province_region": "SP", + "country": "BR", + "postal_code": "04538-132", + "legal_name": "Empresa Exemplo Ltda", + "alternate_name": "Exemplo", + "formation_date": "2010-05-20T00:00:00.000Z", + "incorporation_doc_file": "https://example.com/image.png", + "proof_of_address_doc_type": "UTILITY_BILL", + "proof_of_address_doc_file": "https://example.com/image.png", + "proof_of_ownership_doc_file": "https://example.com/image.png", + "tos_id": "to_nppX66ntvtHs", + "website": "https://site.com/", + "owners": [ + { + "role": "beneficial_owner", + "first_name": "Carlos", + "last_name": "Silva", + "date_of_birth": "1995-05-15T00:00:00.000Z", + "tax_id": "12345678901", + "address_line_1": "Rua Augusta, 1500", + "address_line_2": None, + "city": "São Paulo", + "state_province_region": "SP", + "country": "BR", + "postal_code": "01304-001", + "id_doc_country": "BR", + "id_doc_type": "PASSPORT", + "id_doc_front_file": "https://example.com/image.png", + "id_doc_back_file": "https://example.com/image.png", + "proof_of_address_doc_type": "UTILITY_BILL", + "proof_of_address_doc_file": "https://example.com/image.png", + "id": "ub_000000000000", + "instance_id": "in_000000000000", + "receiver_id": "re_IOxAUL24LG7P", + }, + ], + } + ) + + assert response["error"] is None + assert response["data"] == {"id": "re_IOxAUL24LG7P"} + + def test_get_receiver(self): + mocked_receiver = { + "id": "re_YuaMcI2B8zbQ", + "type": "individual", + "kyc_type": "enhanced", + "kyc_status": "verifying", + "kyc_warnings": [ + { + "code": None, + "message": None, + "resolution_status": None, + "warning_id": None, + }, + ], + "email": "bernardo.simonassi@gmail.com", + "tax_id": "12345678900", + "address_line_1": "Av. Paulista, 1000", + "address_line_2": "Apto 101", + "city": "São Paulo", + "state_province_region": "SP", + "country": "BR", + "postal_code": "01310-100", + "ip_address": "127.0.0.1", + "image_url": "https://example.com/image.png", + "phone_number": "+5511987654321", + "proof_of_address_doc_type": "UTILITY_BILL", + "proof_of_address_doc_file": "https://example.com/image.png", + "first_name": "Bernardo", + "last_name": "Simonassi", + "date_of_birth": "1998-02-02T00:00:00.000Z", + "id_doc_country": "BR", + "id_doc_type": "PASSPORT", + "id_doc_front_file": "https://example.com/image.png", + "id_doc_back_file": "https://example.com/image.png", + "aiprise_validation_key": "", + "source_of_funds_doc_type": "savings", + "source_of_funds_doc_file": "https://example.com/image.png", + "individual_holding_doc_front_file": "https://example.com/image.png", + "purpose_of_transactions": "personal_or_living_expenses", + "purpose_of_transactions_explanation": "I am receiving salary payments from my employer", + "instance_id": "in_000000000000", + "tos_id": "to_3ZZhllJkvo5Z", + "created_at": "2021-01-01T00:00:00.000Z", + "updated_at": "2021-01-01T00:00:00.000Z", + "limit": { + "per_transaction": 100000, + "daily": 200000, + "monthly": 1000000, + }, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_receiver, "error": None} + + response = self.blindpay.receivers.get("re_YuaMcI2B8zbQ") + + assert response["error"] is None + assert response["data"] == mocked_receiver + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/receivers/re_YuaMcI2B8zbQ") + + def test_update_receiver(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = self.blindpay.receivers.update( + { + "receiver_id": "re_YuaMcI2B8zbQ", + "email": "bernardo.simonassi@gmail.com", + "tax_id": "12345678900", + "address_line_1": "Av. Paulista, 1000", + "address_line_2": "Apto 101", + "city": "São Paulo", + "state_province_region": "SP", + "country": "BR", + "postal_code": "01310-100", + "ip_address": "127.0.0.1", + "image_url": "https://example.com/image.png", + "phone_number": "+5511987654321", + "proof_of_address_doc_type": "UTILITY_BILL", + "proof_of_address_doc_file": "https://example.com/image.png", + "first_name": "Bernardo", + "last_name": "Simonassi", + "date_of_birth": "1998-02-02T00:00:00.000Z", + "id_doc_country": "BR", + "id_doc_type": "PASSPORT", + "id_doc_front_file": "https://example.com/image.png", + "id_doc_back_file": "https://example.com/image.png", + "alternate_name": "Exemplo", + "formation_date": "2010-05-20T00:00:00.000Z", + "website": "https://site.com", + "owners": [ + { + "id": "ub_000000000000", + "first_name": "Carlos", + "last_name": "Silva", + "role": "beneficial_owner", + "date_of_birth": "1995-05-15T00:00:00.000Z", + "tax_id": "12345678901", + "address_line_1": "Rua Augusta, 1500", + "address_line_2": None, + "city": "São Paulo", + "state_province_region": "SP", + "country": "BR", + "postal_code": "01304-001", + "id_doc_country": "BR", + "id_doc_type": "PASSPORT", + "id_doc_front_file": "https://example.com/image.png", + "id_doc_back_file": "https://example.com/image.png", + }, + ], + "incorporation_doc_file": "https://example.com/image.png", + "proof_of_ownership_doc_file": "https://example.com/image.png", + "source_of_funds_doc_type": "savings", + "source_of_funds_doc_file": "https://example.com/image.png", + "individual_holding_doc_front_file": "https://example.com/image.png", + "purpose_of_transactions": "personal_or_living_expenses", + "purpose_of_transactions_explanation": "I am receiving salary payments from my employer", + "external_id": "some-external-id", + "tos_id": "to_3ZZhllJkvo5Z", + } + ) + + assert response["error"] is None + assert response["data"] == {"data": None} + + def test_delete_receiver(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = self.blindpay.receivers.delete("re_YuaMcI2B8zbQ") + + assert response["error"] is None + assert response["data"] == {"data": None} + mock_request.assert_called_once_with("DELETE", "/instances/in_000000000000/receivers/re_YuaMcI2B8zbQ", None) + + def test_get_receiver_limits(self): + mocked_receiver_limits = { + "limits": { + "payin": { + "daily": 10000, + "monthly": 50000, + }, + "payout": { + "daily": 20000, + "monthly": 100000, + }, + }, + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_receiver_limits, "error": None} + + response = self.blindpay.receivers.get_limits("re_YuaMcI2B8zbQ") + + assert response["error"] is None + assert response["data"] == mocked_receiver_limits + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/limits/receivers/re_YuaMcI2B8zbQ") + + def test_get_limit_increase_requests(self): + mocked_limit_increase_requests = [ + { + "id": "rl_000000000000", + "receiver_id": "re_YuaMcI2B8zbQ", + "status": "in_review", + "daily": 50000, + "monthly": 250000, + "per_transaction": 25000, + "supporting_document_file": "https://example.com/bank-statement.pdf", + "supporting_document_type": "individual_bank_statement", + "created_at": "2025-01-15T10:30:00.000Z", + "updated_at": "2025-01-15T10:30:00.000Z", + }, + { + "id": "rl_000000000000", + "receiver_id": "re_YuaMcI2B8zbQ", + "status": "approved", + "daily": 30000, + "monthly": 150000, + "per_transaction": 15000, + "supporting_document_file": "https://example.com/proof-of-income.pdf", + "supporting_document_type": "individual_proof_of_income", + "created_at": "2024-12-10T14:20:00.000Z", + "updated_at": "2024-12-12T09:45:00.000Z", + }, + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_limit_increase_requests, "error": None} + + response = self.blindpay.receivers.get_limit_increase_requests("re_YuaMcI2B8zbQ") + + assert response["error"] is None + assert response["data"] == mocked_limit_increase_requests + mock_request.assert_called_once_with( + "GET", "/instances/in_000000000000/receivers/re_YuaMcI2B8zbQ/limit-increase" + ) + + def test_request_limit_increase(self): + mocked_response = { + "id": "rl_000000000000", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_response, "error": None} + + response = self.blindpay.receivers.request_limit_increase( + { + "receiver_id": "re_YuaMcI2B8zbQ", + "daily": 100000, + "monthly": 500000, + "per_transaction": 50000, + "supporting_document_file": "https://example.com/tax-return.pdf", + "supporting_document_type": "individual_tax_return", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_response + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/receivers/re_YuaMcI2B8zbQ/limit-increase", + { + "daily": 100000, + "monthly": 500000, + "per_transaction": 50000, + "supporting_document_file": "https://example.com/tax-return.pdf", + "supporting_document_type": "individual_tax_return", + }, + ) diff --git a/tests/resources/test_terms_of_service.py b/tests/resources/test_terms_of_service.py new file mode 100644 index 0000000..eb84db7 --- /dev/null +++ b/tests/resources/test_terms_of_service.py @@ -0,0 +1,74 @@ +from unittest.mock import patch + +import pytest + +from blindpay import BlindPay, BlindPaySync + + +class TestTermsOfService: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPay(api_key="test-key", instance_id="in_000000000000") + + @pytest.mark.asyncio + async def test_initiate_terms_of_service(self): + mocked_url = { + "url": "https://app.blindpay.com/e/terms-of-service?session_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_url, "error": None} + + response = await self.blindpay.instances.terms_of_service.initiate( + { + "idempotency_key": "123e4567-e89b-12d3-a456-426614174000", + "receiver_id": None, + "redirect_url": None, + } + ) + + assert response["error"] is None + assert response["data"] == mocked_url + mock_request.assert_called_once_with( + "POST", + "/e/instances/in_000000000000/tos", + { + "idempotency_key": "123e4567-e89b-12d3-a456-426614174000", + "receiver_id": None, + "redirect_url": None, + }, + ) + + +class TestTermsOfServiceSync: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPaySync(api_key="test-key", instance_id="in_000000000000") + + def test_initiate_terms_of_service(self): + mocked_url = { + "url": "https://app.blindpay.com/e/terms-of-service?session_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_url, "error": None} + + response = self.blindpay.instances.terms_of_service.initiate( + { + "idempotency_key": "123e4567-e89b-12d3-a456-426614174000", + "receiver_id": None, + "redirect_url": None, + } + ) + + assert response["error"] is None + assert response["data"] == mocked_url + mock_request.assert_called_once_with( + "POST", + "/e/instances/in_000000000000/tos", + { + "idempotency_key": "123e4567-e89b-12d3-a456-426614174000", + "receiver_id": None, + "redirect_url": None, + }, + ) diff --git a/tests/resources/test_virtual_accounts.py b/tests/resources/test_virtual_accounts.py new file mode 100644 index 0000000..ee403c4 --- /dev/null +++ b/tests/resources/test_virtual_accounts.py @@ -0,0 +1,252 @@ +from unittest.mock import patch + +import pytest + +from blindpay import BlindPay, BlindPaySync + + +class TestVirtualAccounts: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPay(api_key="test-key", instance_id="in_000000000000") + + @pytest.mark.asyncio + async def test_update_virtual_account(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = await self.blindpay.virtual_accounts.update( + { + "receiver_id": "re_000000000000", + "blockchain_wallet_id": "bw_000000000000", + "token": "USDC", + } + ) + + assert response["error"] is None + assert response["data"] == {"data": None} + mock_request.assert_called_once_with( + "PUT", + "/instances/in_000000000000/receivers/re_000000000000/virtual-accounts", + {"blockchain_wallet_id": "bw_000000000000", "token": "USDC"}, + ) + + @pytest.mark.asyncio + async def test_create_virtual_account(self): + mocked_virtual_account = { + "id": "va_000000000000", + "us": { + "ach": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "wire": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "rtp": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "swift_bic_code": "CHASUS33", + "account_type": "Business checking", + "beneficiary": { + "name": "Receiver Name", + "address_line_1": "8 The Green", + "address_line_2": "Dover, DE 19901", + }, + "receiving_bank": { + "name": "JPMorgan Chase", + "address_line_1": "270 Park Ave", + "address_line_2": "New York, NY, 10017-2070", + }, + }, + "token": "USDC", + "blockchain_wallet_id": "bw_000000000000", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_virtual_account, "error": None} + + response = await self.blindpay.virtual_accounts.create( + { + "receiver_id": "re_000000000000", + "blockchain_wallet_id": "bw_000000000000", + "token": "USDC", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_virtual_account + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/receivers/re_000000000000/virtual-accounts", + {"blockchain_wallet_id": "bw_000000000000", "token": "USDC"}, + ) + + @pytest.mark.asyncio + async def test_get_virtual_account(self): + mocked_virtual_account = { + "id": "va_000000000000", + "us": { + "ach": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "wire": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "rtp": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "swift_bic_code": "CHASUS33", + "account_type": "Business checking", + "beneficiary": { + "name": "Receiver Name", + "address_line_1": "8 The Green", + "address_line_2": "Dover, DE 19901", + }, + "receiving_bank": { + "name": "JPMorgan Chase", + "address_line_1": "270 Park Ave", + "address_line_2": "New York, NY, 10017-2070", + }, + }, + "token": "USDC", + "blockchain_wallet_id": "bw_000000000000", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_virtual_account, "error": None} + + response = await self.blindpay.virtual_accounts.get("re_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_virtual_account + mock_request.assert_called_once_with( + "GET", "/instances/in_000000000000/receivers/re_000000000000/virtual-accounts" + ) + + +class TestVirtualAccountsSync: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPaySync(api_key="test-key", instance_id="in_000000000000") + + def test_update_virtual_account(self): + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": {"data": None}, "error": None} + + response = self.blindpay.virtual_accounts.update( + { + "receiver_id": "re_000000000000", + "blockchain_wallet_id": "bw_000000000000", + "token": "USDC", + } + ) + + assert response["error"] is None + assert response["data"] == {"data": None} + mock_request.assert_called_once_with( + "PUT", + "/instances/in_000000000000/receivers/re_000000000000/virtual-accounts", + {"blockchain_wallet_id": "bw_000000000000", "token": "USDC"}, + ) + + def test_create_virtual_account(self): + mocked_virtual_account = { + "id": "va_000000000000", + "us": { + "ach": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "wire": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "rtp": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "swift_bic_code": "CHASUS33", + "account_type": "Business checking", + "beneficiary": { + "name": "Receiver Name", + "address_line_1": "8 The Green", + "address_line_2": "Dover, DE 19901", + }, + "receiving_bank": { + "name": "JPMorgan Chase", + "address_line_1": "270 Park Ave", + "address_line_2": "New York, NY, 10017-2070", + }, + }, + "token": "USDC", + "blockchain_wallet_id": "bw_000000000000", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_virtual_account, "error": None} + + response = self.blindpay.virtual_accounts.create( + { + "receiver_id": "re_000000000000", + "blockchain_wallet_id": "bw_000000000000", + "token": "USDC", + } + ) + + assert response["error"] is None + assert response["data"] == mocked_virtual_account + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/receivers/re_000000000000/virtual-accounts", + {"blockchain_wallet_id": "bw_000000000000", "token": "USDC"}, + ) + + def test_get_virtual_account(self): + mocked_virtual_account = { + "id": "va_000000000000", + "us": { + "ach": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "wire": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "rtp": { + "routing_number": "123456789", + "account_number": "123456789", + }, + "swift_bic_code": "CHASUS33", + "account_type": "Business checking", + "beneficiary": { + "name": "Receiver Name", + "address_line_1": "8 The Green", + "address_line_2": "Dover, DE 19901", + }, + "receiving_bank": { + "name": "JPMorgan Chase", + "address_line_1": "270 Park Ave", + "address_line_2": "New York, NY, 10017-2070", + }, + }, + "token": "USDC", + "blockchain_wallet_id": "bw_000000000000", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_virtual_account, "error": None} + + response = self.blindpay.virtual_accounts.get("re_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_virtual_account + mock_request.assert_called_once_with( + "GET", "/instances/in_000000000000/receivers/re_000000000000/virtual-accounts" + ) diff --git a/tests/resources/test_webhooks.py b/tests/resources/test_webhooks.py new file mode 100644 index 0000000..f0e462c --- /dev/null +++ b/tests/resources/test_webhooks.py @@ -0,0 +1,192 @@ +from unittest.mock import patch + +import pytest + +from blindpay import BlindPay, BlindPaySync + + +class TestWebhookEndpoints: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPay(api_key="test-key", instance_id="in_000000000000") + + @pytest.mark.asyncio + async def test_create_webhook_endpoint(self): + mocked_webhook_endpoint = { + "id": "we_000000000000", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_webhook_endpoint, "error": None} + + response = await self.blindpay.instances.webhook_endpoints.create( + { + "url": "https://example.com/webhook", + "events": ["receiver.new"], + } + ) + + assert response["error"] is None + assert response["data"] == mocked_webhook_endpoint + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/webhook-endpoints", + {"url": "https://example.com/webhook", "events": ["receiver.new"]}, + ) + + @pytest.mark.asyncio + async def test_list_webhook_endpoints(self): + mocked_webhook_endpoints = [ + { + "id": "we_000000000000", + "url": "https://example.com/webhook", + "events": ["receiver.new"], + "last_event_at": "2024-01-01T00:00:00.000Z", + "instance_id": "in_000000000000", + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + } + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_webhook_endpoints, "error": None} + + response = await self.blindpay.instances.webhook_endpoints.list() + + assert response["error"] is None + assert response["data"] == mocked_webhook_endpoints + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/webhook-endpoints") + + @pytest.mark.asyncio + async def test_delete_webhook_endpoint(self): + mocked_response = {"data": None} + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_response, "error": None} + + response = await self.blindpay.instances.webhook_endpoints.delete("we_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_response + mock_request.assert_called_once_with( + "DELETE", "/instances/in_000000000000/webhook-endpoints/we_000000000000", None + ) + + @pytest.mark.asyncio + async def test_get_webhook_secret(self): + mocked_webhook_secret = {"key": "whsec_000000000000"} + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_webhook_secret, "error": None} + + response = await self.blindpay.instances.webhook_endpoints.get_secret("we_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_webhook_secret + mock_request.assert_called_once_with( + "GET", "/instances/in_000000000000/webhook-endpoints/we_000000000000/secret" + ) + + @pytest.mark.asyncio + async def test_get_portal_access_url(self): + mocked_webhook_url = {"url": "https://example.com/webhook"} + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_webhook_url, "error": None} + + response = await self.blindpay.instances.webhook_endpoints.get_portal_access_url() + + assert response["error"] is None + assert response["data"] == mocked_webhook_url + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/webhook-endpoints/portal-access") + + +class TestWebhookEndpointsSync: + @pytest.fixture(autouse=True) + def setup(self): + self.blindpay = BlindPaySync(api_key="test-key", instance_id="in_000000000000") + + def test_create_webhook_endpoint(self): + mocked_webhook_endpoint = { + "id": "we_000000000000", + } + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_webhook_endpoint, "error": None} + + response = self.blindpay.instances.webhook_endpoints.create( + { + "url": "https://example.com/webhook", + "events": ["receiver.new"], + } + ) + + assert response["error"] is None + assert response["data"] == mocked_webhook_endpoint + mock_request.assert_called_once_with( + "POST", + "/instances/in_000000000000/webhook-endpoints", + {"url": "https://example.com/webhook", "events": ["receiver.new"]}, + ) + + def test_list_webhook_endpoints(self): + mocked_webhook_endpoints = [ + { + "id": "we_000000000000", + "url": "https://example.com/webhook", + "events": ["receiver.new"], + "last_event_at": "2024-01-01T00:00:00.000Z", + "instance_id": "in_000000000000", + "created_at": "2021-01-01T00:00:00Z", + "updated_at": "2021-01-01T00:00:00Z", + } + ] + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_webhook_endpoints, "error": None} + + response = self.blindpay.instances.webhook_endpoints.list() + + assert response["error"] is None + assert response["data"] == mocked_webhook_endpoints + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/webhook-endpoints") + + def test_delete_webhook_endpoint(self): + mocked_response = {"data": None} + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_response, "error": None} + + response = self.blindpay.instances.webhook_endpoints.delete("we_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_response + mock_request.assert_called_once_with( + "DELETE", "/instances/in_000000000000/webhook-endpoints/we_000000000000", None + ) + + def test_get_webhook_secret(self): + mocked_webhook_secret = {"key": "whsec_000000000000"} + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_webhook_secret, "error": None} + + response = self.blindpay.instances.webhook_endpoints.get_secret("we_000000000000") + + assert response["error"] is None + assert response["data"] == mocked_webhook_secret + mock_request.assert_called_once_with( + "GET", "/instances/in_000000000000/webhook-endpoints/we_000000000000/secret" + ) + + def test_get_portal_access_url(self): + mocked_webhook_url = {"url": "https://example.com/webhook"} + + with patch.object(self.blindpay._api, "_request") as mock_request: + mock_request.return_value = {"data": mocked_webhook_url, "error": None} + + response = self.blindpay.instances.webhook_endpoints.get_portal_access_url() + + assert response["error"] is None + assert response["data"] == mocked_webhook_url + mock_request.assert_called_once_with("GET", "/instances/in_000000000000/webhook-endpoints/portal-access") diff --git a/tests/test_client.py b/tests/test_client.py new file mode 100644 index 0000000..443b4ab --- /dev/null +++ b/tests/test_client.py @@ -0,0 +1,81 @@ +import base64 +import hashlib +import hmac + +from blindpay import BlindPay, BlindPaySync + + +class TestBlindPayClient: + def test_verify_webhook_signature_valid(self): + client = BlindPay(api_key="test-key", instance_id="in_000000000000") + + secret_key = b"test_secret_key_1234" # Must be properly encoded + secret = "whsec_" + base64.b64encode(secret_key).decode() + webhook_id = "msg_123456" + timestamp = "1614556800" + payload = '{"event":"receiver.new","data":{"id":"rec_000000000000"}}' + + signed_content = f"{webhook_id}.{timestamp}.{payload}" + expected_signature = base64.b64encode( + hmac.new(secret_key, signed_content.encode(), hashlib.sha256).digest() + ).decode() + + is_valid = client.verify_webhook_signature( + secret=secret, id=webhook_id, timestamp=timestamp, payload=payload, svix_signature=expected_signature + ) + + assert is_valid is True + + def test_verify_webhook_signature_invalid(self): + client = BlindPay(api_key="test-key", instance_id="in_000000000000") + + secret = "whsec_" + base64.b64encode(b"test_secret_key_1234").decode() + webhook_id = "msg_123456" + timestamp = "1614556800" + payload = '{"event":"receiver.new","data":{"id":"rec_000000000000"}}' + invalid_signature = "invalid_signature_value" + + is_valid = client.verify_webhook_signature( + secret=secret, id=webhook_id, timestamp=timestamp, payload=payload, svix_signature=invalid_signature + ) + + assert is_valid is False + + +class TestBlindPaySyncClient: + def test_sync_client_verify_webhook_signature(self): + """Test webhook signature verification in sync client with valid signature.""" + client = BlindPaySync(api_key="test-key", instance_id="in_000000000000") + + secret_key = b"test_secret_key_1234" # Must be properly encoded + secret = "whsec_" + base64.b64encode(secret_key).decode() + webhook_id = "msg_123456" + timestamp = "1614556800" + payload = '{"event":"receiver.new","data":{"id":"rec_000000000000"}}' + + signed_content = f"{webhook_id}.{timestamp}.{payload}" + expected_signature = base64.b64encode( + hmac.new(secret_key, signed_content.encode(), hashlib.sha256).digest() + ).decode() + + is_valid = client.verify_webhook_signature( + secret=secret, id=webhook_id, timestamp=timestamp, payload=payload, svix_signature=expected_signature + ) + + assert is_valid is True + + def test_sync_client_verify_webhook_signature_invalid(self): + """Test webhook signature verification in sync client with invalid signature.""" + client = BlindPaySync(api_key="test-key", instance_id="in_000000000000") + + secret = "whsec_" + base64.b64encode(b"test_secret_key_1234").decode() + webhook_id = "msg_123456" + timestamp = "1614556800" + payload = '{"event":"receiver.new","data":{"id":"rec_000000000000"}}' + invalid_signature = "invalid_signature_value" + + is_valid = client.verify_webhook_signature( + secret=secret, id=webhook_id, timestamp=timestamp, payload=payload, svix_signature=invalid_signature + ) + + assert is_valid is False diff --git a/uv.lock b/uv.lock index a33bd08..565a494 100644 --- a/uv.lock +++ b/uv.lock @@ -27,7 +27,7 @@ wheels = [ [[package]] name = "blindpay" -version = "1.0.0" +version = "1.2.0" source = { editable = "." } dependencies = [ { name = "httpx" }, @@ -41,6 +41,11 @@ dev = [ { name = "pyright" }, { name = "ruff" }, ] +test = [ + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, +] [package.metadata] requires-dist = [ @@ -55,6 +60,11 @@ dev = [ { name = "pyright", specifier = "==1.1.399" }, { name = "ruff" }, ] +test = [ + { name = "pytest", specifier = ">=7.0.0" }, + { name = "pytest-asyncio", specifier = ">=0.21.0" }, + { name = "pytest-cov", specifier = ">=4.0.0" }, +] [[package]] name = "certifi" @@ -65,6 +75,89 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, ] +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.11.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/59/9698d57a3b11704c7b89b21d69e9d23ecf80d538cabb536c8b63f4a12322/coverage-7.11.3.tar.gz", hash = "sha256:0f59387f5e6edbbffec2281affb71cdc85e0776c1745150a3ab9b6c1d016106b", size = 815210, upload-time = "2025-11-10T00:13:17.18Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/39/af056ec7a27c487e25c7f6b6e51d2ee9821dba1863173ddf4dc2eebef4f7/coverage-7.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b771b59ac0dfb7f139f70c85b42717ef400a6790abb6475ebac1ecee8de782f", size = 216676, upload-time = "2025-11-10T00:11:11.566Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f8/21126d34b174d037b5d01bea39077725cbb9a0da94a95c5f96929c695433/coverage-7.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:603c4414125fc9ae9000f17912dcfd3d3eb677d4e360b85206539240c96ea76e", size = 217034, upload-time = "2025-11-10T00:11:13.12Z" }, + { url = "https://files.pythonhosted.org/packages/d5/3f/0fd35f35658cdd11f7686303214bd5908225838f374db47f9e457c8d6df8/coverage-7.11.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:77ffb3b7704eb7b9b3298a01fe4509cef70117a52d50bcba29cffc5f53dd326a", size = 248531, upload-time = "2025-11-10T00:11:15.023Z" }, + { url = "https://files.pythonhosted.org/packages/8f/59/0bfc5900fc15ce4fd186e092451de776bef244565c840c9c026fd50857e1/coverage-7.11.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4d4ca49f5ba432b0755ebb0fc3a56be944a19a16bb33802264bbc7311622c0d1", size = 251290, upload-time = "2025-11-10T00:11:16.628Z" }, + { url = "https://files.pythonhosted.org/packages/71/88/d5c184001fa2ac82edf1b8f2cd91894d2230d7c309e937c54c796176e35b/coverage-7.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05fd3fb6edff0c98874d752013588836f458261e5eba587afe4c547bba544afd", size = 252375, upload-time = "2025-11-10T00:11:18.249Z" }, + { url = "https://files.pythonhosted.org/packages/5c/29/f60af9f823bf62c7a00ce1ac88441b9a9a467e499493e5cc65028c8b8dd2/coverage-7.11.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e920567f8c3a3ce68ae5a42cf7c2dc4bb6cc389f18bff2235dd8c03fa405de5", size = 248946, upload-time = "2025-11-10T00:11:20.202Z" }, + { url = "https://files.pythonhosted.org/packages/67/16/4662790f3b1e03fce5280cad93fd18711c35980beb3c6f28dca41b5230c6/coverage-7.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4bec8c7160688bd5a34e65c82984b25409563134d63285d8943d0599efbc448e", size = 250310, upload-time = "2025-11-10T00:11:21.689Z" }, + { url = "https://files.pythonhosted.org/packages/8f/75/dd6c2e28308a83e5fc1ee602f8204bd3aa5af685c104cb54499230cf56db/coverage-7.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:adb9b7b42c802bd8cb3927de8c1c26368ce50c8fdaa83a9d8551384d77537044", size = 248461, upload-time = "2025-11-10T00:11:23.384Z" }, + { url = "https://files.pythonhosted.org/packages/16/fe/b71af12be9f59dc9eb060688fa19a95bf3223f56c5af1e9861dfa2275d2c/coverage-7.11.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c8f563b245b4ddb591e99f28e3cd140b85f114b38b7f95b2e42542f0603eb7d7", size = 248039, upload-time = "2025-11-10T00:11:25.07Z" }, + { url = "https://files.pythonhosted.org/packages/11/b8/023b2003a2cd96bdf607afe03d9b96c763cab6d76e024abe4473707c4eb8/coverage-7.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e2a96fdc7643c9517a317553aca13b5cae9bad9a5f32f4654ce247ae4d321405", size = 249903, upload-time = "2025-11-10T00:11:26.992Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ee/5f1076311aa67b1fa4687a724cc044346380e90ce7d94fec09fd384aa5fd/coverage-7.11.3-cp312-cp312-win32.whl", hash = "sha256:e8feeb5e8705835f0622af0fe7ff8d5cb388948454647086494d6c41ec142c2e", size = 219201, upload-time = "2025-11-10T00:11:28.619Z" }, + { url = "https://files.pythonhosted.org/packages/4f/24/d21688f48fe9fcc778956680fd5aaf69f4e23b245b7c7a4755cbd421d25b/coverage-7.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:abb903ffe46bd319d99979cdba350ae7016759bb69f47882242f7b93f3356055", size = 220012, upload-time = "2025-11-10T00:11:30.234Z" }, + { url = "https://files.pythonhosted.org/packages/4f/9e/d5eb508065f291456378aa9b16698b8417d87cb084c2b597f3beb00a8084/coverage-7.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:1451464fd855d9bd000c19b71bb7dafea9ab815741fb0bd9e813d9b671462d6f", size = 218652, upload-time = "2025-11-10T00:11:32.165Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f6/d8572c058211c7d976f24dab71999a565501fb5b3cdcb59cf782f19c4acb/coverage-7.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84b892e968164b7a0498ddc5746cdf4e985700b902128421bb5cec1080a6ee36", size = 216694, upload-time = "2025-11-10T00:11:34.296Z" }, + { url = "https://files.pythonhosted.org/packages/4a/f6/b6f9764d90c0ce1bce8d995649fa307fff21f4727b8d950fa2843b7b0de5/coverage-7.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f761dbcf45e9416ec4698e1a7649248005f0064ce3523a47402d1bff4af2779e", size = 217065, upload-time = "2025-11-10T00:11:36.281Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8d/a12cb424063019fd077b5be474258a0ed8369b92b6d0058e673f0a945982/coverage-7.11.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1410bac9e98afd9623f53876fae7d8a5db9f5a0ac1c9e7c5188463cb4b3212e2", size = 248062, upload-time = "2025-11-10T00:11:37.903Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9c/dab1a4e8e75ce053d14259d3d7485d68528a662e286e184685ea49e71156/coverage-7.11.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:004cdcea3457c0ea3233622cd3464c1e32ebba9b41578421097402bee6461b63", size = 250657, upload-time = "2025-11-10T00:11:39.509Z" }, + { url = "https://files.pythonhosted.org/packages/3f/89/a14f256438324f33bae36f9a1a7137729bf26b0a43f5eda60b147ec7c8c7/coverage-7.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f067ada2c333609b52835ca4d4868645d3b63ac04fb2b9a658c55bba7f667d3", size = 251900, upload-time = "2025-11-10T00:11:41.372Z" }, + { url = "https://files.pythonhosted.org/packages/04/07/75b0d476eb349f1296486b1418b44f2d8780cc8db47493de3755e5340076/coverage-7.11.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07bc7745c945a6d95676953e86ba7cebb9f11de7773951c387f4c07dc76d03f5", size = 248254, upload-time = "2025-11-10T00:11:43.27Z" }, + { url = "https://files.pythonhosted.org/packages/5a/4b/0c486581fa72873489ca092c52792d008a17954aa352809a7cbe6cf0bf07/coverage-7.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bba7e4743e37484ae17d5c3b8eb1ce78b564cb91b7ace2e2182b25f0f764cb5", size = 250041, upload-time = "2025-11-10T00:11:45.274Z" }, + { url = "https://files.pythonhosted.org/packages/af/a3/0059dafb240ae3e3291f81b8de00e9c511d3dd41d687a227dd4b529be591/coverage-7.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbffc22d80d86fbe456af9abb17f7a7766e7b2101f7edaacc3535501691563f7", size = 248004, upload-time = "2025-11-10T00:11:46.93Z" }, + { url = "https://files.pythonhosted.org/packages/83/93/967d9662b1eb8c7c46917dcc7e4c1875724ac3e73c3cb78e86d7a0ac719d/coverage-7.11.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0dba4da36730e384669e05b765a2c49f39514dd3012fcc0398dd66fba8d746d5", size = 247828, upload-time = "2025-11-10T00:11:48.563Z" }, + { url = "https://files.pythonhosted.org/packages/4c/1c/5077493c03215701e212767e470b794548d817dfc6247a4718832cc71fac/coverage-7.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ae12fe90b00b71a71b69f513773310782ce01d5f58d2ceb2b7c595ab9d222094", size = 249588, upload-time = "2025-11-10T00:11:50.581Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a5/77f64de461016e7da3e05d7d07975c89756fe672753e4cf74417fc9b9052/coverage-7.11.3-cp313-cp313-win32.whl", hash = "sha256:12d821de7408292530b0d241468b698bce18dd12ecaf45316149f53877885f8c", size = 219223, upload-time = "2025-11-10T00:11:52.184Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1c/ec51a3c1a59d225b44bdd3a4d463135b3159a535c2686fac965b698524f4/coverage-7.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:6bb599052a974bb6cedfa114f9778fedfad66854107cf81397ec87cb9b8fbcf2", size = 220033, upload-time = "2025-11-10T00:11:53.871Z" }, + { url = "https://files.pythonhosted.org/packages/01/ec/e0ce39746ed558564c16f2cc25fa95ce6fc9fa8bfb3b9e62855d4386b886/coverage-7.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:bb9d7efdb063903b3fdf77caec7b77c3066885068bdc0d44bc1b0c171033f944", size = 218661, upload-time = "2025-11-10T00:11:55.597Z" }, + { url = "https://files.pythonhosted.org/packages/46/cb/483f130bc56cbbad2638248915d97b185374d58b19e3cc3107359715949f/coverage-7.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fb58da65e3339b3dbe266b607bb936efb983d86b00b03eb04c4ad5b442c58428", size = 217389, upload-time = "2025-11-10T00:11:57.59Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ae/81f89bae3afef75553cf10e62feb57551535d16fd5859b9ee5a2a97ddd27/coverage-7.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d16bbe566e16a71d123cd66382c1315fcd520c7573652a8074a8fe281b38c6a", size = 217742, upload-time = "2025-11-10T00:11:59.519Z" }, + { url = "https://files.pythonhosted.org/packages/db/6e/a0fb897041949888191a49c36afd5c6f5d9f5fd757e0b0cd99ec198a324b/coverage-7.11.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8258f10059b5ac837232c589a350a2df4a96406d6d5f2a09ec587cbdd539655", size = 259049, upload-time = "2025-11-10T00:12:01.592Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/d13acc67eb402d91eb94b9bd60593411799aed09ce176ee8d8c0e39c94ca/coverage-7.11.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c5627429f7fbff4f4131cfdd6abd530734ef7761116811a707b88b7e205afd7", size = 261113, upload-time = "2025-11-10T00:12:03.639Z" }, + { url = "https://files.pythonhosted.org/packages/ea/07/a6868893c48191d60406df4356aa7f0f74e6de34ef1f03af0d49183e0fa1/coverage-7.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:465695268414e149bab754c54b0c45c8ceda73dd4a5c3ba255500da13984b16d", size = 263546, upload-time = "2025-11-10T00:12:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/24/e5/28598f70b2c1098332bac47925806353b3313511d984841111e6e760c016/coverage-7.11.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ebcddfcdfb4c614233cff6e9a3967a09484114a8b2e4f2c7a62dc83676ba13f", size = 258260, upload-time = "2025-11-10T00:12:07.137Z" }, + { url = "https://files.pythonhosted.org/packages/0e/58/58e2d9e6455a4ed746a480c4b9cf96dc3cb2a6b8f3efbee5efd33ae24b06/coverage-7.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13b2066303a1c1833c654d2af0455bb009b6e1727b3883c9964bc5c2f643c1d0", size = 261121, upload-time = "2025-11-10T00:12:09.138Z" }, + { url = "https://files.pythonhosted.org/packages/17/57/38803eefb9b0409934cbc5a14e3978f0c85cb251d2b6f6a369067a7105a0/coverage-7.11.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d8750dd20362a1b80e3cf84f58013d4672f89663aee457ea59336df50fab6739", size = 258736, upload-time = "2025-11-10T00:12:11.195Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f3/f94683167156e93677b3442be1d4ca70cb33718df32a2eea44a5898f04f6/coverage-7.11.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ab6212e62ea0e1006531a2234e209607f360d98d18d532c2fa8e403c1afbdd71", size = 257625, upload-time = "2025-11-10T00:12:12.843Z" }, + { url = "https://files.pythonhosted.org/packages/87/ed/42d0bf1bc6bfa7d65f52299a31daaa866b4c11000855d753857fe78260ac/coverage-7.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b17c2b5e0b9bb7702449200f93e2d04cb04b1414c41424c08aa1e5d352da76", size = 259827, upload-time = "2025-11-10T00:12:15.128Z" }, + { url = "https://files.pythonhosted.org/packages/d3/76/5682719f5d5fbedb0c624c9851ef847407cae23362deb941f185f489c54e/coverage-7.11.3-cp313-cp313t-win32.whl", hash = "sha256:426559f105f644b69290ea414e154a0d320c3ad8a2bb75e62884731f69cf8e2c", size = 219897, upload-time = "2025-11-10T00:12:17.274Z" }, + { url = "https://files.pythonhosted.org/packages/10/e0/1da511d0ac3d39e6676fa6cc5ec35320bbf1cebb9b24e9ee7548ee4e931a/coverage-7.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:90a96fcd824564eae6137ec2563bd061d49a32944858d4bdbae5c00fb10e76ac", size = 220959, upload-time = "2025-11-10T00:12:19.292Z" }, + { url = "https://files.pythonhosted.org/packages/e5/9d/e255da6a04e9ec5f7b633c54c0fdfa221a9e03550b67a9c83217de12e96c/coverage-7.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:1e33d0bebf895c7a0905fcfaff2b07ab900885fc78bba2a12291a2cfbab014cc", size = 219234, upload-time = "2025-11-10T00:12:21.251Z" }, + { url = "https://files.pythonhosted.org/packages/84/d6/634ec396e45aded1772dccf6c236e3e7c9604bc47b816e928f32ce7987d1/coverage-7.11.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fdc5255eb4815babcdf236fa1a806ccb546724c8a9b129fd1ea4a5448a0bf07c", size = 216746, upload-time = "2025-11-10T00:12:23.089Z" }, + { url = "https://files.pythonhosted.org/packages/28/76/1079547f9d46f9c7c7d0dad35b6873c98bc5aa721eeabceafabd722cd5e7/coverage-7.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fe3425dc6021f906c6325d3c415e048e7cdb955505a94f1eb774dafc779ba203", size = 217077, upload-time = "2025-11-10T00:12:24.863Z" }, + { url = "https://files.pythonhosted.org/packages/2d/71/6ad80d6ae0d7cb743b9a98df8bb88b1ff3dc54491508a4a97549c2b83400/coverage-7.11.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4ca5f876bf41b24378ee67c41d688155f0e54cdc720de8ef9ad6544005899240", size = 248122, upload-time = "2025-11-10T00:12:26.553Z" }, + { url = "https://files.pythonhosted.org/packages/20/1d/784b87270784b0b88e4beec9d028e8d58f73ae248032579c63ad2ac6f69a/coverage-7.11.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9061a3e3c92b27fd8036dafa26f25d95695b6aa2e4514ab16a254f297e664f83", size = 250638, upload-time = "2025-11-10T00:12:28.555Z" }, + { url = "https://files.pythonhosted.org/packages/f5/26/b6dd31e23e004e9de84d1a8672cd3d73e50f5dae65dbd0f03fa2cdde6100/coverage-7.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abcea3b5f0dc44e1d01c27090bc32ce6ffb7aa665f884f1890710454113ea902", size = 251972, upload-time = "2025-11-10T00:12:30.246Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ef/f9c64d76faac56b82daa036b34d4fe9ab55eb37f22062e68e9470583e688/coverage-7.11.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:68c4eb92997dbaaf839ea13527be463178ac0ddd37a7ac636b8bc11a51af2428", size = 248147, upload-time = "2025-11-10T00:12:32.195Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/5b666f90a8f8053bd264a1ce693d2edef2368e518afe70680070fca13ecd/coverage-7.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:149eccc85d48c8f06547534068c41d69a1a35322deaa4d69ba1561e2e9127e75", size = 249995, upload-time = "2025-11-10T00:12:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/eb/7b/871e991ffb5d067f8e67ffb635dabba65b231d6e0eb724a4a558f4a702a5/coverage-7.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:08c0bcf932e47795c49f0406054824b9d45671362dfc4269e0bc6e4bff010704", size = 247948, upload-time = "2025-11-10T00:12:36.341Z" }, + { url = "https://files.pythonhosted.org/packages/0a/8b/ce454f0af9609431b06dbe5485fc9d1c35ddc387e32ae8e374f49005748b/coverage-7.11.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:39764c6167c82d68a2d8c97c33dba45ec0ad9172570860e12191416f4f8e6e1b", size = 247770, upload-time = "2025-11-10T00:12:38.167Z" }, + { url = "https://files.pythonhosted.org/packages/61/8f/79002cb58a61dfbd2085de7d0a46311ef2476823e7938db80284cedd2428/coverage-7.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3224c7baf34e923ffc78cb45e793925539d640d42c96646db62dbd61bbcfa131", size = 249431, upload-time = "2025-11-10T00:12:40.354Z" }, + { url = "https://files.pythonhosted.org/packages/58/cc/d06685dae97468ed22999440f2f2f5060940ab0e7952a7295f236d98cce7/coverage-7.11.3-cp314-cp314-win32.whl", hash = "sha256:c713c1c528284d636cd37723b0b4c35c11190da6f932794e145fc40f8210a14a", size = 219508, upload-time = "2025-11-10T00:12:42.231Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ed/770cd07706a3598c545f62d75adf2e5bd3791bffccdcf708ec383ad42559/coverage-7.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:c381a252317f63ca0179d2c7918e83b99a4ff3101e1b24849b999a00f9cd4f86", size = 220325, upload-time = "2025-11-10T00:12:44.065Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ac/6a1c507899b6fb1b9a56069954365f655956bcc648e150ce64c2b0ecbed8/coverage-7.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:3e33a968672be1394eded257ec10d4acbb9af2ae263ba05a99ff901bb863557e", size = 218899, upload-time = "2025-11-10T00:12:46.18Z" }, + { url = "https://files.pythonhosted.org/packages/9a/58/142cd838d960cd740654d094f7b0300d7b81534bb7304437d2439fb685fb/coverage-7.11.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f9c96a29c6d65bd36a91f5634fef800212dff69dacdb44345c4c9783943ab0df", size = 217471, upload-time = "2025-11-10T00:12:48.392Z" }, + { url = "https://files.pythonhosted.org/packages/bc/2c/2f44d39eb33e41ab3aba80571daad32e0f67076afcf27cb443f9e5b5a3ee/coverage-7.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2ec27a7a991d229213c8070d31e3ecf44d005d96a9edc30c78eaeafaa421c001", size = 217742, upload-time = "2025-11-10T00:12:50.182Z" }, + { url = "https://files.pythonhosted.org/packages/32/76/8ebc66c3c699f4de3174a43424c34c086323cd93c4930ab0f835731c443a/coverage-7.11.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:72c8b494bd20ae1c58528b97c4a67d5cfeafcb3845c73542875ecd43924296de", size = 259120, upload-time = "2025-11-10T00:12:52.451Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/78a3302b9595f331b86e4f12dfbd9252c8e93d97b8631500888f9a3a2af7/coverage-7.11.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:60ca149a446da255d56c2a7a813b51a80d9497a62250532598d249b3cdb1a926", size = 261229, upload-time = "2025-11-10T00:12:54.667Z" }, + { url = "https://files.pythonhosted.org/packages/07/59/1a9c0844dadef2a6efac07316d9781e6c5a3f3ea7e5e701411e99d619bfd/coverage-7.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb5069074db19a534de3859c43eec78e962d6d119f637c41c8e028c5ab3f59dd", size = 263642, upload-time = "2025-11-10T00:12:56.841Z" }, + { url = "https://files.pythonhosted.org/packages/37/86/66c15d190a8e82eee777793cabde730640f555db3c020a179625a2ad5320/coverage-7.11.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac5d5329c9c942bbe6295f4251b135d860ed9f86acd912d418dce186de7c19ac", size = 258193, upload-time = "2025-11-10T00:12:58.687Z" }, + { url = "https://files.pythonhosted.org/packages/c7/c7/4a4aeb25cb6f83c3ec4763e5f7cc78da1c6d4ef9e22128562204b7f39390/coverage-7.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e22539b676fafba17f0a90ac725f029a309eb6e483f364c86dcadee060429d46", size = 261107, upload-time = "2025-11-10T00:13:00.502Z" }, + { url = "https://files.pythonhosted.org/packages/ed/91/b986b5035f23cf0272446298967ecdd2c3c0105ee31f66f7e6b6948fd7f8/coverage-7.11.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2376e8a9c889016f25472c452389e98bc6e54a19570b107e27cde9d47f387b64", size = 258717, upload-time = "2025-11-10T00:13:02.747Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c7/6c084997f5a04d050c513545d3344bfa17bd3b67f143f388b5757d762b0b/coverage-7.11.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4234914b8c67238a3c4af2bba648dc716aa029ca44d01f3d51536d44ac16854f", size = 257541, upload-time = "2025-11-10T00:13:04.689Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c5/38e642917e406930cb67941210a366ccffa767365c8f8d9ec0f465a8b218/coverage-7.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0b4101e2b3c6c352ff1f70b3a6fcc7c17c1ab1a91ccb7a33013cb0782af9820", size = 259872, upload-time = "2025-11-10T00:13:06.559Z" }, + { url = "https://files.pythonhosted.org/packages/b7/67/5e812979d20c167f81dbf9374048e0193ebe64c59a3d93d7d947b07865fa/coverage-7.11.3-cp314-cp314t-win32.whl", hash = "sha256:305716afb19133762e8cf62745c46c4853ad6f9eeba54a593e373289e24ea237", size = 220289, upload-time = "2025-11-10T00:13:08.635Z" }, + { url = "https://files.pythonhosted.org/packages/24/3a/b72573802672b680703e0df071faadfab7dcd4d659aaaffc4626bc8bbde8/coverage-7.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9245bd392572b9f799261c4c9e7216bafc9405537d0f4ce3ad93afe081a12dc9", size = 221398, upload-time = "2025-11-10T00:13:10.734Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4e/649628f28d38bad81e4e8eb3f78759d20ac173e3c456ac629123815feb40/coverage-7.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:9a1d577c20b4334e5e814c3d5fe07fa4a8c3ae42a601945e8d7940bab811d0bd", size = 219435, upload-time = "2025-11-10T00:13:12.712Z" }, + { url = "https://files.pythonhosted.org/packages/19/8f/92bdd27b067204b99f396a1414d6342122f3e2663459baf787108a6b8b84/coverage-7.11.3-py3-none-any.whl", hash = "sha256:351511ae28e2509c8d8cae5311577ea7dd511ab8e746ffc8814a0896c3d33fbe", size = 208478, upload-time = "2025-11-10T00:13:14.908Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -111,6 +204,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + [[package]] name = "mypy" version = "1.18.2" @@ -161,6 +263,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, ] +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + [[package]] name = "pathspec" version = "0.12.1" @@ -170,6 +281,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, ] +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + [[package]] name = "pydantic" version = "2.11.10" @@ -227,6 +347,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, ] +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + [[package]] name = "pyright" version = "1.1.399" @@ -240,6 +369,49 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2f/b5/380380c9e7a534cb1783c70c3e8ac6d1193c599650a55838d0557586796e/pyright-1.1.399-py3-none-any.whl", hash = "sha256:55f9a875ddf23c9698f24208c764465ffdfd38be6265f7faf9a176e1dc549f3b", size = 5592584, upload-time = "2025-04-10T04:40:23.502Z" }, ] +[[package]] +name = "pytest" +version = "9.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/56/f013048ac4bc4c1d9be45afd4ab209ea62822fb1598f40687e6bf45dcea4/pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8", size = 1564125, upload-time = "2025-11-12T13:05:09.333Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad", size = 373668, upload-time = "2025-11-12T13:05:07.379Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, +] + [[package]] name = "ruff" version = "0.13.3"