mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-08-10 03:07:06 +00:00
resolve review comments
This commit is contained in:
+5
-1
@@ -441,7 +441,11 @@ async def credit_balance(
|
||||
.where(col(db.ApiKey.hashed_key) == key.hashed_key)
|
||||
.values(balance=(db.ApiKey.balance) + amount)
|
||||
)
|
||||
await session.exec(stmt) # type: ignore[call-overload]
|
||||
result = await session.exec(stmt) # type: ignore[call-overload]
|
||||
# If pruning removed this key after redemption, do not commit a no-op
|
||||
# balance update and pretend the top-up succeeded.
|
||||
if (getattr(result, "rowcount", 0) or 0) == 0:
|
||||
raise ValueError("API key disappeared before credit could be recorded")
|
||||
await session.commit()
|
||||
await session.refresh(key)
|
||||
|
||||
|
||||
@@ -262,3 +262,22 @@ async def test_transaction_audit_trail_preserved(patched_db_engine: None) -> Non
|
||||
assert surviving is not None, "Financial audit row must survive key deletion"
|
||||
assert surviving.api_key_hashed_key is None, "Link must be nulled, not dangling"
|
||||
assert surviving.amount == 21
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_periodic_prune_disabled_returns_immediately(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""Non-positive intervals disable the janitor."""
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from routstr import auth
|
||||
from routstr.core.settings import settings
|
||||
|
||||
monkeypatch.setattr(settings, "dead_key_prune_interval_seconds", 0)
|
||||
sleep_mock = AsyncMock()
|
||||
monkeypatch.setattr(auth.asyncio, "sleep", sleep_mock)
|
||||
|
||||
await auth.periodic_dead_key_prune()
|
||||
|
||||
sleep_mock.assert_not_called()
|
||||
|
||||
@@ -150,6 +150,7 @@ async def test_credit_balance() -> None:
|
||||
mock_key.balance = 5000000
|
||||
mock_key.hashed_key = "test_hash"
|
||||
mock_session = AsyncMock()
|
||||
mock_session.exec.return_value.rowcount = 1
|
||||
|
||||
# Mock session.refresh to update the balance (simulates DB reload)
|
||||
async def mock_refresh(key: ApiKey) -> None:
|
||||
@@ -206,6 +207,38 @@ async def test_credit_balance_rejects_zero_amount() -> None:
|
||||
assert not mock_session.commit.called
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_credit_balance_rejects_missing_key() -> None:
|
||||
"""A top-up must fail if the key was pruned after redemption."""
|
||||
token_data = {
|
||||
"token": [{"mint": "http://mint:3338", "proofs": [{"amount": 1000}]}],
|
||||
"unit": "sat",
|
||||
}
|
||||
token_json = json.dumps(token_data)
|
||||
token_b64 = base64.urlsafe_b64encode(token_json.encode()).decode()
|
||||
token_str = f"cashuA{token_b64}"
|
||||
|
||||
mock_key = Mock()
|
||||
mock_key.balance = 0
|
||||
mock_key.hashed_key = "test_hash"
|
||||
mock_session = AsyncMock()
|
||||
mock_session.exec.return_value.rowcount = 0
|
||||
|
||||
from routstr.core.settings import settings
|
||||
|
||||
with patch.object(settings, "cashu_mints", ["http://mint:3338"]):
|
||||
with patch(
|
||||
"routstr.wallet.recieve_token",
|
||||
return_value=(1000, "sat", "http://mint:3338"),
|
||||
):
|
||||
with pytest.raises(ValueError, match="disappeared"):
|
||||
await credit_balance(token_str, mock_key, mock_session)
|
||||
|
||||
# UPDATE matched nothing; committing would hide the failed credit.
|
||||
assert mock_session.exec.called
|
||||
assert not mock_session.commit.called
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_swap_to_primary_mint_insufficient_for_fees() -> None:
|
||||
"""Token amount is less than melt_quote.amount + melt_quote.fee_reserve."""
|
||||
|
||||
Reference in New Issue
Block a user