Files
Leonid PershinandCursor 0bad693b12 Enhance Selectel balance handling and error messaging in GPU rental system
- Updated Selectel documentation to clarify error responses related to balance issues.
- Introduced `peek_balance_rub` function for best-effort balance checks, handling API failures gracefully.
- Improved error messages for insufficient funds and quota issues, specifying Selectel's 403 policy response.
- Added balance checks in the doctor command to ensure users are informed about their balance status before attempting GPU creation.
- Refactored exception handling in cloud operations to provide clearer feedback on balance-related errors.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-23 17:54:11 +03:00

73 lines
2.3 KiB
Python

from gpu_rent.balance import (
BalanceWatchState,
balance_rub_from_payload,
peek_balance_rub,
plan_balance_notices,
spend_steps,
)
from gpu_rent.errors import CloudError
def test_balance_kopecks():
payload = {
"data": {
"billings": [
{"balances_values_sum": 123456}, # 1234.56 ₽
]
}
}
assert balance_rub_from_payload(payload) == 1234.56
def test_spend_steps_200():
assert spend_steps(1000, 850, 200) == 0
assert spend_steps(1000, 800, 200) == 1
assert spend_steps(1000, 600, 200) == 2
assert spend_steps(1000, 1100, 200) == 0 # top-up direction
def test_plan_notifies_each_step():
state = BalanceWatchState(baseline_rub=1000.0)
state, msgs = plan_balance_notices(state, 750.0, step_rub=200.0)
assert state.last_notified_step == 1
assert len(msgs) == 1
assert "200" in msgs[0]
state, msgs2 = plan_balance_notices(state, 550.0, step_rub=200.0)
assert state.last_notified_step == 2
assert len(msgs2) == 1
# no re-notify same step
state, msgs3 = plan_balance_notices(state, 520.0, step_rub=200.0)
assert msgs3 == []
assert state.last_notified_step == 2
def test_plan_topup_resets():
state = BalanceWatchState(baseline_rub=500.0, last_notified_step=2)
state, msgs = plan_balance_notices(state, 900.0, step_rub=200.0)
assert state.baseline_rub == 900.0
assert state.last_notified_step == 0
assert any("пополнен" in m for m in msgs)
def test_plan_low_once():
state = BalanceWatchState(baseline_rub=1000.0)
state, msgs = plan_balance_notices(state, 150.0, step_rub=200.0, low_rub=200.0)
assert state.low_notified
assert any("низкий" in m for m in msgs)
state, msgs2 = plan_balance_notices(state, 100.0, step_rub=200.0, low_rub=200.0)
assert not any("низкий" in m for m in msgs2)
def test_peek_balance_rub_empty_token():
assert peek_balance_rub("") is None
assert peek_balance_rub(None) is None
def test_peek_balance_rub_swallows_errors(monkeypatch):
def boom(*_a, **_k):
raise CloudError("сеть")
monkeypatch.setattr("gpu_rent.balance.fetch_balance_rub", boom)
assert peek_balance_rub("tok") is None