mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-08-09 11:04:36 +00:00
134 lines
4.3 KiB
Python
134 lines
4.3 KiB
Python
"""Coverage tests for admin.py (currently 35%).
|
|
|
|
Tests admin endpoints that are testable without full app setup:
|
|
withdraw validation, authentication guards, and slug validation.
|
|
"""
|
|
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
from fastapi import HTTPException, Request
|
|
|
|
# ===========================================================================
|
|
# withdraw — validation and edge cases
|
|
# ===========================================================================
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_withdraw_rejects_zero_amount() -> None:
|
|
"""withdraw validation rejects amount <= 0."""
|
|
from routstr.core.admin import WithdrawRequest, withdraw
|
|
|
|
request = Request(scope={"type": "http", "method": "POST"})
|
|
|
|
with pytest.raises(HTTPException) as exc_info:
|
|
await withdraw(request, WithdrawRequest(amount=0, unit="sat"))
|
|
|
|
assert exc_info.value.status_code == 400
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_withdraw_rejects_negative_amount() -> None:
|
|
"""withdraw validation rejects negative amounts."""
|
|
from routstr.core.admin import WithdrawRequest, withdraw
|
|
|
|
request = Request(scope={"type": "http", "method": "POST"})
|
|
|
|
with pytest.raises(HTTPException) as exc_info:
|
|
await withdraw(request, WithdrawRequest(amount=-100, unit="sat"))
|
|
|
|
assert exc_info.value.status_code == 400
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_withdraw_rejects_insufficient_balance() -> None:
|
|
"""withdraw returns 400 when wallet balance is insufficient."""
|
|
from routstr.core.admin import WithdrawRequest, withdraw
|
|
|
|
request = Request(scope={"type": "http", "method": "POST"})
|
|
|
|
with patch(
|
|
"routstr.core.admin.send_token",
|
|
new=AsyncMock(
|
|
side_effect=ValueError(
|
|
"No trusted mint has 1000000 sat available; balances={}"
|
|
)
|
|
),
|
|
):
|
|
with pytest.raises(HTTPException) as exc_info:
|
|
await withdraw(request, WithdrawRequest(amount=1000000, unit="sat"))
|
|
|
|
assert exc_info.value.status_code == 400
|
|
assert "Insufficient" in str(exc_info.value.detail)
|
|
|
|
|
|
# ===========================================================================
|
|
# require_admin_api guard
|
|
# ===========================================================================
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_require_admin_rejects_no_session() -> None:
|
|
"""require_admin_api rejects requests without admin session cookie."""
|
|
from routstr.core.admin import require_admin_api
|
|
|
|
request = Request(scope={
|
|
"type": "http",
|
|
"method": "GET",
|
|
"headers": [],
|
|
})
|
|
|
|
with pytest.raises(HTTPException) as exc_info:
|
|
await require_admin_api(request)
|
|
|
|
# 401 or 403 depending on auth configuration
|
|
assert exc_info.value.status_code in (401, 403)
|
|
|
|
|
|
# ===========================================================================
|
|
# _validate_slug
|
|
# ===========================================================================
|
|
|
|
def test_validate_slug_accepts_valid() -> None:
|
|
"""Valid slugs pass validation."""
|
|
from routstr.core.admin import _validate_slug
|
|
|
|
assert _validate_slug("valid-slug") == "valid-slug"
|
|
assert _validate_slug("valid123") == "valid123"
|
|
assert _validate_slug("my-provider") == "my-provider"
|
|
|
|
|
|
def test_validate_slug_rejects_spaces() -> None:
|
|
"""Slugs with spaces are rejected."""
|
|
from fastapi import HTTPException
|
|
|
|
from routstr.core.admin import _validate_slug
|
|
|
|
with pytest.raises(HTTPException):
|
|
_validate_slug("invalid slug")
|
|
|
|
|
|
def test_validate_slug_rejects_too_short() -> None:
|
|
"""Slugs shorter than 3 chars are rejected."""
|
|
from fastapi import HTTPException
|
|
|
|
from routstr.core.admin import _validate_slug
|
|
|
|
with pytest.raises(HTTPException):
|
|
_validate_slug("ab")
|
|
|
|
|
|
# ===========================================================================
|
|
# admin login endpoint
|
|
# ===========================================================================
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_admin_login_requires_payload() -> None:
|
|
"""admin_login requires a payload — verify it exists."""
|
|
# Verify the function signature
|
|
import inspect
|
|
|
|
from routstr.core.admin import admin_login
|
|
sig = inspect.signature(admin_login)
|
|
params = list(sig.parameters.keys())
|
|
assert "request" in params
|
|
assert "payload" in params or len(params) >= 2
|