Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
uses: ./.github/workflows/typecheck.yaml

tests:
name: Tests
needs: [lint, typecheck]
uses: ./.github/workflows/tests.yaml
35 changes: 35 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -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

4 changes: 2 additions & 2 deletions .github/workflows/typecheck.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: lint-check lint-fix typecheck
.PHONY: lint-check lint-fix typecheck test

lint-check:
uv run ruff format --check
Expand All @@ -11,3 +11,6 @@ lint-fix:
typecheck:
uv run pyright
uv run mypy .

test:
uv run pytest
13 changes: 13 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
13 changes: 10 additions & 3 deletions pyrightconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"include": ["src"],
"include": ["src", "tests"],
"exclude": [".venv", ".git", "examples/**", "**/__pycache__"],
"typeCheckingMode": "strict",
"pythonVersion": "3.12",
Expand All @@ -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"
}
]
}
13 changes: 13 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -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

1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

5 changes: 5 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import sys
from pathlib import Path

src_path = Path(__file__).parent.parent / "src"
sys.path.insert(0, str(src_path))
1 change: 1 addition & 0 deletions tests/resources/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

188 changes: 188 additions & 0 deletions tests/resources/test_api_keys.py
Original file line number Diff line number Diff line change
@@ -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)
Loading