feat: parse nested pydantic models fetchone and fetchall + add shortcuts for insert_query and update_query into Database (#2714)
* feat: add shortcuts for insert_query and update_query into `Database`
example: await db.insert("table_name", base_model)
* remove where from argument
* chore: code clean-up
* extension manager
* lnbits-qrcode components
* parse date from dict
* refactor: make `settings` a fixture
* chore: remove verbose key names
* fix: time column
* fix: cast balance to `int`
* extension toggle vue3
* vue3 @input migration
* fix: payment extra and payment hash
* fix dynamic fields and ext db migration
* remove shadow on cards in dark theme
* screwed up and made more css pushes to this branch
* attempt to make chip component in settings dynamic fields
* dynamic chips
* qrscanner
* clean init admin settings
* make get_user better
* add dbversion model
* remove update_payment_status/extra/details
* traces for value and assertion errors
* refactor services
* add PaymentFiatAmount
* return Payment on api endpoints
* rename to get_user_from_account
* refactor: just refactor (#2740)
* rc5
* Fix db cache (#2741)
* [refactor] split services.py (#2742)
* refactor: spit `core.py` (#2743)
* refactor: make QR more customizable
* fix: print.html
* fix: qrcode options
* fix: white shadow on dark theme
* fix: datetime wasnt parsed in dict_to_model
* add timezone for conversion
* only parse timestamp for sqlite, postgres does it
* log internal payment success
* fix: export wallet to phone QR
* Adding a customisable border theme, like gradient (#2746)
* fixed mobile scan btn
* fix test websocket
* fix get_payments tests
* dict_to_model skip none values
* preimage none instead of defaulting to 0000...
* fixup test real invoice tests
* fixed pheonixd for wss
* fix nodemanager test settings
* fix lnbits funding
* only insert extension when they dont exist
---------
Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
Co-authored-by: Tiago Vasconcelos <talvasconcelos@gmail.com>
Co-authored-by: Arc <ben@arc.wales>
Co-authored-by: Arc <33088785+arcbtc@users.noreply.github.com>
This commit is contained in:
parent
ae4eda04ba
commit
2940cf97c5
84 changed files with 4220 additions and 3776 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import pytest
|
||||
|
||||
from lnbits.settings import settings
|
||||
from lnbits.core.models import User
|
||||
from lnbits.settings import Settings
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -18,7 +19,7 @@ async def test_admin_get_settings(client, superuser):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_update_settings(client, superuser):
|
||||
async def test_admin_update_settings(client, superuser: User, settings: Settings):
|
||||
new_site_title = "UPDATED SITETITLE"
|
||||
response = await client.put(
|
||||
f"/admin/api/v1/settings?usr={superuser.id}",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import pytest
|
|||
from lnbits import bolt11
|
||||
from lnbits.core.models import CreateInvoice, Payment
|
||||
from lnbits.core.views.payment_api import api_payment
|
||||
from lnbits.settings import settings
|
||||
from lnbits.settings import Settings
|
||||
|
||||
from ..helpers import (
|
||||
get_random_invoice_data,
|
||||
|
|
@ -14,10 +14,13 @@ from ..helpers import (
|
|||
|
||||
# create account POST /api/v1/account
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_account(client):
|
||||
async def test_create_account(client, settings: Settings):
|
||||
settings.lnbits_allow_new_accounts = False
|
||||
response = await client.post("/api/v1/account", json={"name": "test"})
|
||||
assert response.status_code == 403
|
||||
|
||||
assert response.status_code == 400
|
||||
assert response.json().get("detail") == "Account creation is disabled."
|
||||
|
||||
settings.lnbits_allow_new_accounts = True
|
||||
response = await client.post("/api/v1/account", json={"name": "test"})
|
||||
assert response.status_code == 200
|
||||
|
|
@ -120,7 +123,7 @@ async def test_create_invoice(client, inkey_headers_to):
|
|||
invoice = response.json()
|
||||
assert "payment_hash" in invoice
|
||||
assert len(invoice["payment_hash"]) == 64
|
||||
assert "payment_request" in invoice
|
||||
assert "bolt11" in invoice
|
||||
assert "checking_id" in invoice
|
||||
assert len(invoice["checking_id"])
|
||||
return invoice
|
||||
|
|
@ -135,7 +138,7 @@ async def test_create_invoice_fiat_amount(client, inkey_headers_to):
|
|||
)
|
||||
assert response.status_code == 201
|
||||
invoice = response.json()
|
||||
decode = bolt11.decode(invoice["payment_request"])
|
||||
decode = bolt11.decode(invoice["bolt11"])
|
||||
assert decode.amount_msat != data["amount"] * 1000
|
||||
assert decode.payment_hash
|
||||
|
||||
|
|
@ -177,7 +180,7 @@ async def test_create_internal_invoice(client, inkey_headers_to):
|
|||
assert response.status_code == 201
|
||||
assert "payment_hash" in invoice
|
||||
assert len(invoice["payment_hash"]) == 64
|
||||
assert "payment_request" in invoice
|
||||
assert "bolt11" in invoice
|
||||
assert "checking_id" in invoice
|
||||
assert len(invoice["checking_id"])
|
||||
return invoice
|
||||
|
|
@ -194,26 +197,28 @@ async def test_create_invoice_custom_expiry(client, inkey_headers_to):
|
|||
)
|
||||
assert response.status_code == 201
|
||||
invoice = response.json()
|
||||
bolt11_invoice = bolt11.decode(invoice["payment_request"])
|
||||
bolt11_invoice = bolt11.decode(invoice["bolt11"])
|
||||
assert bolt11_invoice.expiry == expiry_seconds
|
||||
|
||||
|
||||
# check POST /api/v1/payments: make payment
|
||||
@pytest.mark.asyncio
|
||||
async def test_pay_invoice(client, from_wallet_ws, invoice, adminkey_headers_from):
|
||||
data = {"out": True, "bolt11": invoice["payment_request"]}
|
||||
async def test_pay_invoice(
|
||||
client, from_wallet_ws, invoice: Payment, adminkey_headers_from
|
||||
):
|
||||
data = {"out": True, "bolt11": invoice.bolt11}
|
||||
response = await client.post(
|
||||
"/api/v1/payments", json=data, headers=adminkey_headers_from
|
||||
)
|
||||
assert response.status_code < 300
|
||||
invoice = response.json()
|
||||
assert len(invoice["payment_hash"]) == 64
|
||||
assert len(invoice["checking_id"]) > 0
|
||||
invoice_ = response.json()
|
||||
assert len(invoice_["payment_hash"]) == 64
|
||||
assert len(invoice_["checking_id"]) > 0
|
||||
|
||||
data = from_wallet_ws.receive_json()
|
||||
assert "wallet_balance" in data
|
||||
payment = Payment(**data["payment"])
|
||||
assert payment.payment_hash == invoice["payment_hash"]
|
||||
ws_data = from_wallet_ws.receive_json()
|
||||
assert "wallet_balance" in ws_data
|
||||
payment = Payment(**ws_data["payment"])
|
||||
assert payment.payment_hash == invoice_["payment_hash"]
|
||||
|
||||
# websocket from to_wallet cant be tested before https://github.com/lnbits/lnbits/pull/1793
|
||||
# data = to_wallet_ws.receive_json()
|
||||
|
|
@ -224,9 +229,9 @@ async def test_pay_invoice(client, from_wallet_ws, invoice, adminkey_headers_fro
|
|||
|
||||
# check GET /api/v1/payments/<hash>: payment status
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_payment_without_key(client, invoice):
|
||||
async def test_check_payment_without_key(client, invoice: Payment):
|
||||
# check the payment status
|
||||
response = await client.get(f"/api/v1/payments/{invoice['payment_hash']}")
|
||||
response = await client.get(f"/api/v1/payments/{invoice.payment_hash}")
|
||||
assert response.status_code < 300
|
||||
assert response.json()["paid"] is True
|
||||
assert invoice
|
||||
|
|
@ -240,10 +245,10 @@ async def test_check_payment_without_key(client, invoice):
|
|||
# If sqlite: it will succeed only with adminkey_headers_to
|
||||
# TODO: fix this
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_payment_with_key(client, invoice, inkey_headers_from):
|
||||
async def test_check_payment_with_key(client, invoice: Payment, inkey_headers_from):
|
||||
# check the payment status
|
||||
response = await client.get(
|
||||
f"/api/v1/payments/{invoice['payment_hash']}", headers=inkey_headers_from
|
||||
f"/api/v1/payments/{invoice.payment_hash}", headers=inkey_headers_from
|
||||
)
|
||||
assert response.status_code < 300
|
||||
assert response.json()["paid"] is True
|
||||
|
|
@ -255,7 +260,7 @@ async def test_check_payment_with_key(client, invoice, inkey_headers_from):
|
|||
# check POST /api/v1/payments: payment with wrong key type
|
||||
@pytest.mark.asyncio
|
||||
async def test_pay_invoice_wrong_key(client, invoice, adminkey_headers_from):
|
||||
data = {"out": True, "bolt11": invoice["payment_request"]}
|
||||
data = {"out": True, "bolt11": invoice.bolt11}
|
||||
# try payment with wrong key
|
||||
wrong_adminkey_headers = adminkey_headers_from.copy()
|
||||
wrong_adminkey_headers["X-Api-Key"] = "wrong_key"
|
||||
|
|
@ -276,7 +281,7 @@ async def test_pay_invoice_self_payment(client, adminkey_headers_from):
|
|||
)
|
||||
assert response.status_code < 300
|
||||
json_data = response.json()
|
||||
data = {"out": True, "bolt11": json_data["payment_request"]}
|
||||
data = {"out": True, "bolt11": json_data["bolt11"]}
|
||||
response = await client.post(
|
||||
"/api/v1/payments", json=data, headers=adminkey_headers_from
|
||||
)
|
||||
|
|
@ -286,7 +291,7 @@ async def test_pay_invoice_self_payment(client, adminkey_headers_from):
|
|||
# check POST /api/v1/payments: payment with invoice key [should fail]
|
||||
@pytest.mark.asyncio
|
||||
async def test_pay_invoice_invoicekey(client, invoice, inkey_headers_from):
|
||||
data = {"out": True, "bolt11": invoice["payment_request"]}
|
||||
data = {"out": True, "bolt11": invoice.bolt11}
|
||||
# try payment with invoice key
|
||||
response = await client.post(
|
||||
"/api/v1/payments", json=data, headers=inkey_headers_from
|
||||
|
|
@ -297,7 +302,7 @@ async def test_pay_invoice_invoicekey(client, invoice, inkey_headers_from):
|
|||
# check POST /api/v1/payments: payment with admin key, trying to pay twice [should fail]
|
||||
@pytest.mark.asyncio
|
||||
async def test_pay_invoice_adminkey(client, invoice, adminkey_headers_from):
|
||||
data = {"out": True, "bolt11": invoice["payment_request"]}
|
||||
data = {"out": True, "bolt11": invoice.bolt11}
|
||||
# try payment with admin key
|
||||
response = await client.post(
|
||||
"/api/v1/payments", json=data, headers=adminkey_headers_from
|
||||
|
|
@ -306,19 +311,20 @@ async def test_pay_invoice_adminkey(client, invoice, adminkey_headers_from):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_payments(client, adminkey_headers_from, fake_payments):
|
||||
async def test_get_payments(client, inkey_fresh_headers_to, fake_payments):
|
||||
fake_data, filters = fake_payments
|
||||
|
||||
async def get_payments(params: dict):
|
||||
response = await client.get(
|
||||
"/api/v1/payments",
|
||||
params=filters | params,
|
||||
headers=adminkey_headers_from,
|
||||
headers=inkey_fresh_headers_to,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
return [Payment(**payment) for payment in response.json()]
|
||||
|
||||
payments = await get_payments({"sortby": "amount", "direction": "desc", "limit": 2})
|
||||
assert len(payments) != 0
|
||||
assert payments[-1].amount < payments[0].amount
|
||||
assert len(payments) == 2
|
||||
|
||||
|
|
@ -340,13 +346,13 @@ async def test_get_payments(client, adminkey_headers_from, fake_payments):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_payments_paginated(client, adminkey_headers_from, fake_payments):
|
||||
async def test_get_payments_paginated(client, inkey_fresh_headers_to, fake_payments):
|
||||
fake_data, filters = fake_payments
|
||||
|
||||
response = await client.get(
|
||||
"/api/v1/payments/paginated",
|
||||
params=filters | {"limit": 2},
|
||||
headers=adminkey_headers_from,
|
||||
headers=inkey_fresh_headers_to,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
paginated = response.json()
|
||||
|
|
@ -355,13 +361,13 @@ async def test_get_payments_paginated(client, adminkey_headers_from, fake_paymen
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_payments_history(client, adminkey_headers_from, fake_payments):
|
||||
async def test_get_payments_history(client, inkey_fresh_headers_to, fake_payments):
|
||||
fake_data, filters = fake_payments
|
||||
|
||||
response = await client.get(
|
||||
"/api/v1/payments/history",
|
||||
params=filters,
|
||||
headers=adminkey_headers_from,
|
||||
headers=inkey_fresh_headers_to,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
|
@ -377,7 +383,7 @@ async def test_get_payments_history(client, adminkey_headers_from, fake_payments
|
|||
response = await client.get(
|
||||
"/api/v1/payments/history?group=INVALID",
|
||||
params=filters,
|
||||
headers=adminkey_headers_from,
|
||||
headers=inkey_fresh_headers_to,
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
|
|
@ -385,21 +391,21 @@ async def test_get_payments_history(client, adminkey_headers_from, fake_payments
|
|||
|
||||
# check POST /api/v1/payments/decode
|
||||
@pytest.mark.asyncio
|
||||
async def test_decode_invoice(client, invoice):
|
||||
data = {"data": invoice["payment_request"]}
|
||||
async def test_decode_invoice(client, invoice: Payment):
|
||||
data = {"data": invoice.bolt11}
|
||||
response = await client.post(
|
||||
"/api/v1/payments/decode",
|
||||
json=data,
|
||||
)
|
||||
assert response.status_code < 300
|
||||
assert response.json()["payment_hash"] == invoice["payment_hash"]
|
||||
assert response.json()["payment_hash"] == invoice.payment_hash
|
||||
|
||||
|
||||
# check api_payment() internal function call (NOT API): payment status
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_payment_without_key(invoice):
|
||||
async def test_api_payment_without_key(invoice: Payment):
|
||||
# check the payment status
|
||||
response = await api_payment(invoice["payment_hash"])
|
||||
response = await api_payment(invoice.payment_hash)
|
||||
assert isinstance(response, dict)
|
||||
assert response["paid"] is True
|
||||
# no key, that's why no "details"
|
||||
|
|
@ -408,11 +414,9 @@ async def test_api_payment_without_key(invoice):
|
|||
|
||||
# check api_payment() internal function call (NOT API): payment status
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_payment_with_key(invoice, inkey_headers_from):
|
||||
async def test_api_payment_with_key(invoice: Payment, inkey_headers_from):
|
||||
# check the payment status
|
||||
response = await api_payment(
|
||||
invoice["payment_hash"], inkey_headers_from["X-Api-Key"]
|
||||
)
|
||||
response = await api_payment(invoice.payment_hash, inkey_headers_from["X-Api-Key"])
|
||||
assert isinstance(response, dict)
|
||||
assert response["paid"] is True
|
||||
assert "details" in response
|
||||
|
|
@ -431,7 +435,7 @@ async def test_create_invoice_with_description_hash(client, inkey_headers_to):
|
|||
)
|
||||
invoice = response.json()
|
||||
|
||||
invoice_bolt11 = bolt11.decode(invoice["payment_request"])
|
||||
invoice_bolt11 = bolt11.decode(invoice["bolt11"])
|
||||
assert invoice_bolt11.description_hash == descr_hash
|
||||
return invoice
|
||||
|
||||
|
|
@ -448,7 +452,7 @@ async def test_create_invoice_with_unhashed_description(client, inkey_headers_to
|
|||
)
|
||||
invoice = response.json()
|
||||
|
||||
invoice_bolt11 = bolt11.decode(invoice["payment_request"])
|
||||
invoice_bolt11 = bolt11.decode(invoice["bolt11"])
|
||||
assert invoice_bolt11.description_hash == descr_hash
|
||||
assert invoice_bolt11.description is None
|
||||
return invoice
|
||||
|
|
@ -475,7 +479,7 @@ async def test_update_wallet(client, adminkey_headers_from):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fiat_tracking(client, adminkey_headers_from):
|
||||
async def test_fiat_tracking(client, adminkey_headers_from, settings: Settings):
|
||||
async def create_invoice():
|
||||
data = await get_random_invoice_data()
|
||||
response = await client.post(
|
||||
|
|
@ -501,13 +505,15 @@ async def test_fiat_tracking(client, adminkey_headers_from):
|
|||
|
||||
settings.lnbits_default_accounting_currency = "USD"
|
||||
payment = await create_invoice()
|
||||
assert payment["extra"]["wallet_fiat_currency"] == "USD"
|
||||
assert payment["extra"]["wallet_fiat_amount"] != payment["amount"]
|
||||
assert payment["extra"]["wallet_fiat_rate"]
|
||||
extra = payment["extra"]
|
||||
assert extra["wallet_fiat_currency"] == "USD"
|
||||
assert extra["wallet_fiat_amount"] != payment["amount"]
|
||||
assert extra["wallet_fiat_rate"]
|
||||
|
||||
await update_currency("EUR")
|
||||
|
||||
payment = await create_invoice()
|
||||
assert payment["extra"]["wallet_fiat_currency"] == "EUR"
|
||||
assert payment["extra"]["wallet_fiat_amount"] != payment["amount"]
|
||||
assert payment["extra"]["wallet_fiat_rate"]
|
||||
extra = payment["extra"]
|
||||
assert extra["wallet_fiat_currency"] == "EUR"
|
||||
assert extra["wallet_fiat_amount"] != payment["amount"]
|
||||
assert extra["wallet_fiat_rate"]
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from httpx import AsyncClient
|
|||
|
||||
from lnbits.core.models import AccessTokenPayload, User
|
||||
from lnbits.core.views.user_api import api_users_reset_password
|
||||
from lnbits.settings import AuthMethods, settings
|
||||
from lnbits.settings import AuthMethods, Settings
|
||||
from lnbits.utils.nostr import hex_to_npub, sign_event
|
||||
|
||||
nostr_event = {
|
||||
|
|
@ -29,8 +29,6 @@ private_key = secp256k1.PrivateKey(
|
|||
)
|
||||
pubkey_hex = private_key.pubkey.serialize().hex()[2:]
|
||||
|
||||
settings.auth_allowed_methods = AuthMethods.all()
|
||||
|
||||
|
||||
################################ LOGIN ################################
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -63,7 +61,9 @@ async def test_login_alan_usr(user_alan: User, http_client: AsyncClient):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_login_usr_not_allowed(user_alan: User, http_client: AsyncClient):
|
||||
async def test_login_usr_not_allowed(
|
||||
user_alan: User, http_client: AsyncClient, settings: Settings
|
||||
):
|
||||
# exclude 'user_id_only'
|
||||
settings.auth_allowed_methods = [AuthMethods.username_and_password.value]
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ async def test_login_usr_not_allowed(user_alan: User, http_client: AsyncClient):
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_login_alan_username_password_ok(
|
||||
user_alan: User, http_client: AsyncClient
|
||||
user_alan: User, http_client: AsyncClient, settings: Settings
|
||||
):
|
||||
response = await http_client.post(
|
||||
"/api/v1/auth", json={"username": user_alan.username, "password": "secret1234"}
|
||||
|
|
@ -95,6 +95,7 @@ async def test_login_alan_username_password_ok(
|
|||
|
||||
payload: dict = jwt.decode(access_token, settings.auth_secret_key, ["HS256"])
|
||||
access_token_payload = AccessTokenPayload(**payload)
|
||||
|
||||
assert access_token_payload.sub == "alan", "Subject is Alan."
|
||||
assert access_token_payload.email == "alan@lnbits.com"
|
||||
assert access_token_payload.auth_time, "Auth time should be set by server."
|
||||
|
|
@ -113,7 +114,9 @@ async def test_login_alan_username_password_ok(
|
|||
assert not user.admin, "Not admin."
|
||||
assert not user.super_user, "Not superuser."
|
||||
assert user.has_password, "Password configured."
|
||||
assert len(user.wallets) == 1, "One default wallet."
|
||||
assert (
|
||||
len(user.wallets) == 1
|
||||
), f"Expected 1 default wallet, not {len(user.wallets)}."
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -139,7 +142,7 @@ async def test_login_alan_password_nok(user_alan: User, http_client: AsyncClient
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_login_username_password_not_allowed(
|
||||
user_alan: User, http_client: AsyncClient
|
||||
user_alan: User, http_client: AsyncClient, settings: Settings
|
||||
):
|
||||
# exclude 'username_password'
|
||||
settings.auth_allowed_methods = [AuthMethods.user_id_only.value]
|
||||
|
|
@ -164,7 +167,7 @@ async def test_login_username_password_not_allowed(
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_login_alan_change_auth_secret_key(
|
||||
user_alan: User, http_client: AsyncClient
|
||||
user_alan: User, http_client: AsyncClient, settings: Settings
|
||||
):
|
||||
response = await http_client.post(
|
||||
"/api/v1/auth", json={"username": user_alan.username, "password": "secret1234"}
|
||||
|
|
@ -221,7 +224,9 @@ async def test_register_ok(http_client: AsyncClient):
|
|||
assert not user.admin, "Not admin."
|
||||
assert not user.super_user, "Not superuser."
|
||||
assert user.has_password, "Password configured."
|
||||
assert len(user.wallets) == 1, "One default wallet."
|
||||
assert (
|
||||
len(user.wallets) == 1
|
||||
), f"Expected 1 default wallet, not {len(user.wallets)}."
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -250,7 +255,8 @@ async def test_register_email_twice(http_client: AsyncClient):
|
|||
"email": f"u21.{tiny_id}@lnbits.com",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 403, "Not allowed."
|
||||
|
||||
assert response.status_code == 400, "Not allowed."
|
||||
assert response.json().get("detail") == "Email already exists."
|
||||
|
||||
|
||||
|
|
@ -280,7 +286,7 @@ async def test_register_username_twice(http_client: AsyncClient):
|
|||
"email": f"u21.{tiny_id_2}@lnbits.com",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 403, "Not allowed."
|
||||
assert response.status_code == 400, "Not allowed."
|
||||
assert response.json().get("detail") == "Username already exists."
|
||||
|
||||
|
||||
|
|
@ -320,7 +326,7 @@ async def test_register_bad_email(http_client: AsyncClient):
|
|||
|
||||
################################ CHANGE PASSWORD ################################
|
||||
@pytest.mark.asyncio
|
||||
async def test_change_password_ok(http_client: AsyncClient):
|
||||
async def test_change_password_ok(http_client: AsyncClient, settings: Settings):
|
||||
tiny_id = shortuuid.uuid()[:8]
|
||||
response = await http_client.post(
|
||||
"/api/v1/auth/register",
|
||||
|
|
@ -409,8 +415,8 @@ async def test_alan_change_password_old_nok(user_alan: User, http_client: AsyncC
|
|||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 403, "Old password bad."
|
||||
assert response.json().get("detail") == "Invalid credentials."
|
||||
assert response.status_code == 400, "Old password bad."
|
||||
assert response.json().get("detail") == "Invalid old password."
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -441,7 +447,7 @@ async def test_alan_change_password_different_user(
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_alan_change_password_auth_threshold_expired(
|
||||
user_alan: User, http_client: AsyncClient
|
||||
user_alan: User, http_client: AsyncClient, settings: Settings
|
||||
):
|
||||
|
||||
response = await http_client.post("/api/v1/auth/usr", json={"usr": user_alan.id})
|
||||
|
|
@ -464,7 +470,7 @@ async def test_alan_change_password_auth_threshold_expired(
|
|||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 403, "Treshold expired."
|
||||
assert response.status_code == 400
|
||||
assert (
|
||||
response.json().get("detail") == "You can only update your credentials"
|
||||
" in the first 1 seconds."
|
||||
|
|
@ -476,7 +482,7 @@ async def test_alan_change_password_auth_threshold_expired(
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_nostr_ok(http_client: AsyncClient):
|
||||
async def test_register_nostr_ok(http_client: AsyncClient, settings: Settings):
|
||||
event = {**nostr_event}
|
||||
event["created_at"] = int(time.time())
|
||||
|
||||
|
|
@ -502,6 +508,7 @@ async def test_register_nostr_ok(http_client: AsyncClient):
|
|||
response = await http_client.get(
|
||||
"/api/v1/auth", headers={"Authorization": f"Bearer {access_token}"}
|
||||
)
|
||||
|
||||
user = User(**response.json())
|
||||
assert user.username is None, "No username."
|
||||
assert user.email is None, "No email."
|
||||
|
|
@ -509,11 +516,13 @@ async def test_register_nostr_ok(http_client: AsyncClient):
|
|||
assert not user.admin, "Not admin."
|
||||
assert not user.super_user, "Not superuser."
|
||||
assert not user.has_password, "Password configured."
|
||||
assert len(user.wallets) == 1, "One default wallet."
|
||||
assert (
|
||||
len(user.wallets) == 1
|
||||
), f"Expected 1 default wallet, not {len(user.wallets)}."
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_nostr_not_allowed(http_client: AsyncClient):
|
||||
async def test_register_nostr_not_allowed(http_client: AsyncClient, settings: Settings):
|
||||
# exclude 'nostr_auth_nip98'
|
||||
settings.auth_allowed_methods = [AuthMethods.username_and_password.value]
|
||||
response = await http_client.post(
|
||||
|
|
@ -540,25 +549,25 @@ async def test_register_nostr_bad_header(http_client: AsyncClient):
|
|||
)
|
||||
|
||||
assert response.status_code == 401, "Non nostr header."
|
||||
assert response.json().get("detail") == "Authorization header is not nostr."
|
||||
assert response.json().get("detail") == "Invalid Authorization scheme."
|
||||
|
||||
response = await http_client.post(
|
||||
"/api/v1/auth/nostr",
|
||||
headers={"Authorization": "nostr xyz"},
|
||||
)
|
||||
assert response.status_code == 401, "Nostr not base64."
|
||||
assert response.status_code == 400, "Nostr not base64."
|
||||
assert response.json().get("detail") == "Nostr login event cannot be parsed."
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_nostr_bad_event(http_client: AsyncClient):
|
||||
async def test_register_nostr_bad_event(http_client: AsyncClient, settings: Settings):
|
||||
settings.auth_allowed_methods = AuthMethods.all()
|
||||
base64_event = base64.b64encode(json.dumps(nostr_event).encode()).decode("ascii")
|
||||
response = await http_client.post(
|
||||
"/api/v1/auth/nostr",
|
||||
headers={"Authorization": f"nostr {base64_event}"},
|
||||
)
|
||||
assert response.status_code == 401, "Nostr event expired."
|
||||
assert response.status_code == 400, "Nostr event expired."
|
||||
assert (
|
||||
response.json().get("detail")
|
||||
== f"More than {settings.auth_credetials_update_threshold}"
|
||||
|
|
@ -574,7 +583,7 @@ async def test_register_nostr_bad_event(http_client: AsyncClient):
|
|||
"/api/v1/auth/nostr",
|
||||
headers={"Authorization": f"nostr {base64_event}"},
|
||||
)
|
||||
assert response.status_code == 401, "Nostr event signature invalid."
|
||||
assert response.status_code == 400, "Nostr event signature invalid."
|
||||
assert response.json().get("detail") == "Nostr login event is not valid."
|
||||
|
||||
|
||||
|
|
@ -591,7 +600,7 @@ async def test_register_nostr_bad_event_kind(http_client: AsyncClient):
|
|||
"/api/v1/auth/nostr",
|
||||
headers={"Authorization": f"nostr {base64_event_bad_kind}"},
|
||||
)
|
||||
assert response.status_code == 401, "Nostr event kind invalid."
|
||||
assert response.status_code == 400, "Nostr event kind invalid."
|
||||
assert response.json().get("detail") == "Invalid event kind."
|
||||
|
||||
|
||||
|
|
@ -610,7 +619,7 @@ async def test_register_nostr_bad_event_tag_u(http_client: AsyncClient):
|
|||
"/api/v1/auth/nostr",
|
||||
headers={"Authorization": f"nostr {base64_event_tag_kind}"},
|
||||
)
|
||||
assert response.status_code == 401, "Nostr event tag missing."
|
||||
assert response.status_code == 400, "Nostr event tag missing."
|
||||
assert response.json().get("detail") == "Tag 'method' is missing."
|
||||
|
||||
event_bad_kind["tags"] = [["u", "http://localhost:5000/nostr"], ["method", "XYZ"]]
|
||||
|
|
@ -623,8 +632,8 @@ async def test_register_nostr_bad_event_tag_u(http_client: AsyncClient):
|
|||
"/api/v1/auth/nostr",
|
||||
headers={"Authorization": f"nostr {base64_event_tag_kind}"},
|
||||
)
|
||||
assert response.status_code == 401, "Nostr event tag invalid."
|
||||
assert response.json().get("detail") == "Incorrect value for tag 'method'."
|
||||
assert response.status_code == 400, "Nostr event tag invalid."
|
||||
assert response.json().get("detail") == "Invalid value for tag 'method'."
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -642,7 +651,7 @@ async def test_register_nostr_bad_event_tag_menthod(http_client: AsyncClient):
|
|||
"/api/v1/auth/nostr",
|
||||
headers={"Authorization": f"nostr {base64_event}"},
|
||||
)
|
||||
assert response.status_code == 401, "Nostr event tag missing."
|
||||
assert response.status_code == 400, "Nostr event tag missing."
|
||||
assert response.json().get("detail") == "Tag 'u' for URL is missing."
|
||||
|
||||
event_bad_kind["tags"] = [["u", "http://demo.lnbits.com/nostr"], ["method", "POST"]]
|
||||
|
|
@ -655,15 +664,15 @@ async def test_register_nostr_bad_event_tag_menthod(http_client: AsyncClient):
|
|||
"/api/v1/auth/nostr",
|
||||
headers={"Authorization": f"nostr {base64_event}"},
|
||||
)
|
||||
assert response.status_code == 401, "Nostr event tag invalid."
|
||||
assert response.status_code == 400, "Nostr event tag invalid."
|
||||
assert (
|
||||
response.json().get("detail") == "Incorrect value for tag 'u':"
|
||||
response.json().get("detail") == "Invalid value for tag 'u':"
|
||||
" 'http://demo.lnbits.com/nostr'."
|
||||
)
|
||||
|
||||
|
||||
################################ CHANGE PUBLIC KEY ################################
|
||||
async def test_change_pubkey_npub_ok(http_client: AsyncClient, user_alan: User):
|
||||
async def test_change_pubkey_npub_ok(http_client: AsyncClient, settings: Settings):
|
||||
tiny_id = shortuuid.uuid()[:8]
|
||||
response = await http_client.post(
|
||||
"/api/v1/auth/register",
|
||||
|
|
@ -703,7 +712,9 @@ async def test_change_pubkey_npub_ok(http_client: AsyncClient, user_alan: User):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_change_pubkey_ok(http_client: AsyncClient, user_alan: User):
|
||||
async def test_change_pubkey_ok(
|
||||
http_client: AsyncClient, user_alan: User, settings: Settings
|
||||
):
|
||||
tiny_id = shortuuid.uuid()[:8]
|
||||
response = await http_client.post(
|
||||
"/api/v1/auth/register",
|
||||
|
|
@ -783,7 +794,7 @@ async def test_change_pubkey_ok(http_client: AsyncClient, user_alan: User):
|
|||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 403, "Pubkey already used."
|
||||
assert response.status_code == 400, "Pubkey already used."
|
||||
assert response.json().get("detail") == "Public key already in use."
|
||||
|
||||
|
||||
|
|
@ -825,7 +836,7 @@ async def test_change_pubkey_other_user(http_client: AsyncClient, user_alan: Use
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_alan_change_pubkey_auth_threshold_expired(
|
||||
user_alan: User, http_client: AsyncClient
|
||||
user_alan: User, http_client: AsyncClient, settings: Settings
|
||||
):
|
||||
|
||||
response = await http_client.post("/api/v1/auth/usr", json={"usr": user_alan.id})
|
||||
|
|
@ -835,7 +846,7 @@ async def test_alan_change_pubkey_auth_threshold_expired(
|
|||
assert access_token is not None
|
||||
|
||||
settings.auth_credetials_update_threshold = 1
|
||||
time.sleep(1.1)
|
||||
time.sleep(2.1)
|
||||
response = await http_client.put(
|
||||
"/api/v1/auth/pubkey",
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
|
|
@ -845,17 +856,17 @@ async def test_alan_change_pubkey_auth_threshold_expired(
|
|||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 403, "Treshold expired."
|
||||
assert response.status_code == 400, "Treshold expired."
|
||||
assert (
|
||||
response.json().get("detail") == "You can only update your credentials"
|
||||
" in the first 1 seconds after login."
|
||||
" Please login again!"
|
||||
" in the first 1 seconds."
|
||||
" Please login again or ask a new reset key!"
|
||||
)
|
||||
|
||||
|
||||
################################ RESET PASSWORD ################################
|
||||
@pytest.mark.asyncio
|
||||
async def test_request_reset_key_ok(http_client: AsyncClient):
|
||||
async def test_request_reset_key_ok(http_client: AsyncClient, settings: Settings):
|
||||
tiny_id = shortuuid.uuid()[:8]
|
||||
response = await http_client.post(
|
||||
"/api/v1/auth/register",
|
||||
|
|
@ -922,12 +933,14 @@ async def test_request_reset_key_user_not_found(http_client: AsyncClient):
|
|||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 403, "User does not exist."
|
||||
assert response.status_code == 404, "User does not exist."
|
||||
assert response.json().get("detail") == "User not found."
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reset_username_password_not_allowed(http_client: AsyncClient):
|
||||
async def test_reset_username_password_not_allowed(
|
||||
http_client: AsyncClient, settings: Settings
|
||||
):
|
||||
# exclude 'username_password'
|
||||
settings.auth_allowed_methods = [AuthMethods.user_id_only.value]
|
||||
|
||||
|
|
@ -968,7 +981,7 @@ async def test_reset_username_passwords_do_not_matcj(
|
|||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 403, "Passwords do not match."
|
||||
assert response.status_code == 400, "Passwords do not match."
|
||||
assert response.json().get("detail") == "Passwords do not match."
|
||||
|
||||
|
||||
|
|
@ -983,13 +996,13 @@ async def test_reset_username_password_bad_key(http_client: AsyncClient):
|
|||
"password_repeat": "secret0000",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 500, "Bad reset key."
|
||||
assert response.json().get("detail") == "Cannot reset user password."
|
||||
assert response.status_code == 400, "Bad reset key."
|
||||
assert response.json().get("detail") == "Invalid reset key."
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reset_password_auth_threshold_expired(
|
||||
user_alan: User, http_client: AsyncClient
|
||||
user_alan: User, http_client: AsyncClient, settings: Settings
|
||||
):
|
||||
|
||||
reset_key = await api_users_reset_password(user_alan.id)
|
||||
|
|
@ -1006,7 +1019,7 @@ async def test_reset_password_auth_threshold_expired(
|
|||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 403, "Treshold expired."
|
||||
assert response.status_code == 400, "Treshold expired."
|
||||
assert (
|
||||
response.json().get("detail") == "You can only update your credentials"
|
||||
" in the first 1 seconds."
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import pytest
|
||||
|
||||
from lnbits.core.models import Payment
|
||||
|
||||
|
||||
# check if the client is working
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -10,17 +12,15 @@ async def test_core_views_generic(client):
|
|||
|
||||
# check GET /public/v1/payment/{payment_hash}: correct hash [should pass]
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_public_payment_longpolling(client, invoice):
|
||||
response = await client.get(f"/public/v1/payment/{invoice['payment_hash']}")
|
||||
async def test_api_public_payment_longpolling(client, invoice: Payment):
|
||||
response = await client.get(f"/public/v1/payment/{invoice.payment_hash}")
|
||||
assert response.status_code < 300
|
||||
assert response.json()["status"] == "paid"
|
||||
|
||||
|
||||
# check GET /public/v1/payment/{payment_hash}: wrong hash [should fail]
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_public_payment_longpolling_wrong_hash(client, invoice):
|
||||
response = await client.get(
|
||||
f"/public/v1/payment/{invoice['payment_hash'] + '0'*64}"
|
||||
)
|
||||
async def test_api_public_payment_longpolling_wrong_hash(client, invoice: Payment):
|
||||
response = await client.get(f"/public/v1/payment/{invoice.payment_hash + '0'*64}")
|
||||
assert response.status_code == 404
|
||||
assert response.json()["detail"] == "Payment does not exist."
|
||||
|
|
|
|||
|
|
@ -1,60 +1,60 @@
|
|||
# ruff: noqa: E402
|
||||
import asyncio
|
||||
from time import time
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import uvloop
|
||||
from asgi_lifespan import LifespanManager
|
||||
|
||||
from lnbits.core.views.payment_api import _api_payments_create_invoice
|
||||
from lnbits.wallets.fake import FakeWallet
|
||||
|
||||
uvloop.install()
|
||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from asgi_lifespan import LifespanManager
|
||||
from fastapi.testclient import TestClient
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
|
||||
from lnbits.app import create_app
|
||||
from lnbits.core.crud import (
|
||||
create_account,
|
||||
create_wallet,
|
||||
delete_account,
|
||||
get_account,
|
||||
get_account_by_username,
|
||||
get_user,
|
||||
update_payment_status,
|
||||
get_payment,
|
||||
get_user_from_account,
|
||||
update_payment,
|
||||
)
|
||||
from lnbits.core.models import CreateInvoice, PaymentState
|
||||
from lnbits.core.models import Account, CreateInvoice, PaymentState, User
|
||||
from lnbits.core.services import create_user_account, update_wallet_balance
|
||||
from lnbits.core.views.payment_api import api_payments_create_invoice
|
||||
from lnbits.db import DB_TYPE, SQLITE, Database
|
||||
from lnbits.settings import AuthMethods, settings
|
||||
from lnbits.settings import AuthMethods, Settings
|
||||
from lnbits.settings import settings as lnbits_settings
|
||||
from tests.helpers import (
|
||||
get_random_invoice_data,
|
||||
)
|
||||
|
||||
# override settings for tests
|
||||
settings.lnbits_data_folder = "./tests/data"
|
||||
settings.lnbits_admin_ui = True
|
||||
settings.lnbits_extensions_default_install = []
|
||||
settings.lnbits_extensions_deactivate_all = True
|
||||
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
def settings():
|
||||
# override settings for tests
|
||||
lnbits_settings.lnbits_admin_extensions = []
|
||||
lnbits_settings.lnbits_data_folder = "./tests/data"
|
||||
lnbits_settings.lnbits_admin_ui = True
|
||||
lnbits_settings.lnbits_extensions_default_install = []
|
||||
lnbits_settings.lnbits_extensions_deactivate_all = True
|
||||
|
||||
yield lnbits_settings
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def run_before_and_after_tests():
|
||||
def run_before_and_after_tests(settings: Settings):
|
||||
"""Fixture to execute asserts before and after a test is run"""
|
||||
##### BEFORE TEST RUN #####
|
||||
|
||||
settings.lnbits_allow_new_accounts = True
|
||||
settings.auth_allowed_methods = AuthMethods.all()
|
||||
settings.auth_credetials_update_threshold = 120
|
||||
settings.lnbits_reserve_fee_percent = 1
|
||||
settings.lnbits_reserve_fee_min = 2000
|
||||
settings.lnbits_service_fee = 0
|
||||
settings.lnbits_wallet_limit_daily_max_withdraw = 0
|
||||
settings.lnbits_admin_extensions = []
|
||||
|
||||
_settings_cleanup(settings)
|
||||
yield # this is where the testing happens
|
||||
|
||||
##### AFTER TEST RUN #####
|
||||
_settings_cleanup(settings)
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
|
|
@ -66,7 +66,7 @@ def event_loop():
|
|||
|
||||
# use session scope to run once before and once after all tests
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
async def app():
|
||||
async def app(settings: Settings):
|
||||
app = create_app()
|
||||
async with LifespanManager(app) as manager:
|
||||
settings.first_install = False
|
||||
|
|
@ -74,7 +74,7 @@ async def app():
|
|||
|
||||
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
async def client(app):
|
||||
async def client(app, settings: Settings):
|
||||
url = f"http://{settings.host}:{settings.port}"
|
||||
async with AsyncClient(transport=ASGITransport(app=app), base_url=url) as client:
|
||||
yield client
|
||||
|
|
@ -82,7 +82,7 @@ async def client(app):
|
|||
|
||||
# function scope
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def http_client(app):
|
||||
async def http_client(app, settings: Settings):
|
||||
url = f"http://{settings.host}:{settings.port}"
|
||||
|
||||
async with AsyncClient(transport=ASGITransport(app=app), base_url=url) as client:
|
||||
|
|
@ -99,25 +99,33 @@ async def db():
|
|||
yield Database("database")
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="package")
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
async def user_alan():
|
||||
user = await get_account_by_username("alan")
|
||||
if not user:
|
||||
user = await create_user_account(
|
||||
email="alan@lnbits.com", username="alan", password="secret1234"
|
||||
)
|
||||
account = await get_account_by_username("alan")
|
||||
if account:
|
||||
await delete_account(account.id)
|
||||
|
||||
account = Account(
|
||||
id=uuid4().hex,
|
||||
email="alan@lnbits.com",
|
||||
username="alan",
|
||||
)
|
||||
account.hash_password("secret1234")
|
||||
user = await create_user_account(account)
|
||||
|
||||
yield user
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
async def from_user():
|
||||
user = await create_account()
|
||||
user = await create_user_account()
|
||||
yield user
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
async def from_wallet(from_user):
|
||||
user = from_user
|
||||
|
||||
wallet = await create_wallet(user_id=user.id, wallet_name="test_wallet_from")
|
||||
await update_wallet_balance(
|
||||
wallet_id=wallet.id,
|
||||
|
|
@ -126,6 +134,15 @@ async def from_wallet(from_user):
|
|||
yield wallet
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
async def to_wallet_pagination_tests(to_user):
|
||||
user = to_user
|
||||
wallet = await create_wallet(
|
||||
user_id=user.id, wallet_name="test_wallet_to_pagination_tests"
|
||||
)
|
||||
yield wallet
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def from_wallet_ws(from_wallet, test_client):
|
||||
# wait a bit in order to avoid receiving topup notification
|
||||
|
|
@ -136,12 +153,12 @@ async def from_wallet_ws(from_wallet, test_client):
|
|||
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
async def to_user():
|
||||
user = await create_account()
|
||||
user = await create_user_account()
|
||||
yield user
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def from_super_user(from_user):
|
||||
def from_super_user(from_user: User, settings: Settings):
|
||||
prev = settings.super_user
|
||||
settings.super_user = from_user.id
|
||||
yield from_user
|
||||
|
|
@ -149,8 +166,10 @@ def from_super_user(from_user):
|
|||
|
||||
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
async def superuser():
|
||||
user = await get_user(settings.super_user)
|
||||
async def superuser(settings: Settings):
|
||||
account = await get_account(settings.super_user)
|
||||
assert account, "Superuser not found"
|
||||
user = await get_user_from_account(account)
|
||||
yield user
|
||||
|
||||
|
||||
|
|
@ -165,6 +184,13 @@ async def to_wallet(to_user):
|
|||
yield wallet
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
async def to_fresh_wallet(to_user):
|
||||
user = to_user
|
||||
wallet = await create_wallet(user_id=user.id, wallet_name="test_wallet_to_fresh")
|
||||
yield wallet
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def to_wallet_ws(to_wallet, test_client):
|
||||
# wait a bit in order to avoid receiving topup notification
|
||||
|
|
@ -173,6 +199,15 @@ async def to_wallet_ws(to_wallet, test_client):
|
|||
yield ws
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
async def inkey_fresh_headers_to(to_fresh_wallet):
|
||||
wallet = to_fresh_wallet
|
||||
yield {
|
||||
"X-Api-Key": wallet.inkey,
|
||||
"Content-type": "application/json",
|
||||
}
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
async def inkey_headers_from(from_wallet):
|
||||
wallet = from_wallet
|
||||
|
|
@ -213,7 +248,7 @@ async def adminkey_headers_to(to_wallet):
|
|||
async def invoice(to_wallet):
|
||||
data = await get_random_invoice_data()
|
||||
invoice_data = CreateInvoice(**data)
|
||||
invoice = await api_payments_create_invoice(invoice_data, to_wallet)
|
||||
invoice = await _api_payments_create_invoice(invoice_data, to_wallet)
|
||||
yield invoice
|
||||
del invoice
|
||||
|
||||
|
|
@ -224,12 +259,14 @@ async def external_funding_source():
|
|||
|
||||
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
async def fake_payments(client, adminkey_headers_from):
|
||||
async def fake_payments(client, inkey_fresh_headers_to):
|
||||
|
||||
ts = datetime.now(timezone.utc).timestamp()
|
||||
|
||||
# Because sqlite only stores timestamps with milliseconds
|
||||
# we have to wait a second to ensure a different timestamp than previous invoices
|
||||
if DB_TYPE == SQLITE:
|
||||
await asyncio.sleep(1)
|
||||
ts = time()
|
||||
|
||||
fake_data = [
|
||||
CreateInvoice(amount=10, memo="aaaa", out=False),
|
||||
|
|
@ -239,12 +276,29 @@ async def fake_payments(client, adminkey_headers_from):
|
|||
|
||||
for invoice in fake_data:
|
||||
response = await client.post(
|
||||
"/api/v1/payments", headers=adminkey_headers_from, json=invoice.dict()
|
||||
"/api/v1/payments", headers=inkey_fresh_headers_to, json=invoice.dict()
|
||||
)
|
||||
assert response.is_success
|
||||
data = response.json()
|
||||
assert data["checking_id"]
|
||||
await update_payment_status(data["checking_id"], status=PaymentState.SUCCESS)
|
||||
payment = await get_payment(data["checking_id"])
|
||||
payment.status = PaymentState.SUCCESS
|
||||
await update_payment(payment)
|
||||
|
||||
params = {"time[ge]": ts, "time[le]": time()}
|
||||
params = {
|
||||
"created_at[ge]": ts,
|
||||
"created_at[le]": datetime.now(timezone.utc).timestamp(),
|
||||
}
|
||||
return fake_data, params
|
||||
|
||||
|
||||
def _settings_cleanup(settings: Settings):
|
||||
settings.lnbits_allow_new_accounts = True
|
||||
settings.lnbits_allowed_users = []
|
||||
settings.auth_allowed_methods = AuthMethods.all()
|
||||
settings.auth_credetials_update_threshold = 120
|
||||
settings.lnbits_reserve_fee_percent = 1
|
||||
settings.lnbits_reserve_fee_min = 2000
|
||||
settings.lnbits_service_fee = 0
|
||||
settings.lnbits_wallet_limit_daily_max_withdraw = 0
|
||||
settings.lnbits_admin_extensions = []
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ import random
|
|||
import string
|
||||
from typing import Optional
|
||||
|
||||
from lnbits.db import FromRowModel
|
||||
from pydantic import BaseModel
|
||||
|
||||
from lnbits.wallets import get_funding_source, set_funding_source
|
||||
|
||||
|
||||
|
|
@ -10,12 +11,26 @@ class FakeError(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class DbTestModel(FromRowModel):
|
||||
class DbTestModel(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
value: Optional[str] = None
|
||||
|
||||
|
||||
class DbTestModel2(BaseModel):
|
||||
id: int
|
||||
label: str
|
||||
description: Optional[str] = None
|
||||
child: DbTestModel
|
||||
|
||||
|
||||
class DbTestModel3(BaseModel):
|
||||
id: int
|
||||
user: str
|
||||
child: DbTestModel2
|
||||
active: bool = False
|
||||
|
||||
|
||||
def get_random_string(iterations: int = 10):
|
||||
return "".join(
|
||||
random.SystemRandom().choice(string.ascii_uppercase + string.digits)
|
||||
|
|
|
|||
|
|
@ -39,9 +39,11 @@ docker_lightning_unconnected_cli = [
|
|||
|
||||
|
||||
def run_cmd(cmd: list) -> str:
|
||||
timeout = 20
|
||||
timeout = 10
|
||||
process = Popen(cmd, stdout=PIPE, stderr=PIPE)
|
||||
|
||||
logger.debug(f"running command: {cmd}")
|
||||
|
||||
def process_communication(comm):
|
||||
stdout, stderr = comm
|
||||
output = stdout.decode("utf-8").strip()
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import hashlib
|
|||
import pytest
|
||||
|
||||
from lnbits import bolt11
|
||||
from lnbits.core.crud import get_standalone_payment, update_payment_details
|
||||
from lnbits.core.crud import get_standalone_payment, update_payment
|
||||
from lnbits.core.models import CreateInvoice, Payment, PaymentState
|
||||
from lnbits.core.services import fee_reserve_total, get_balance_delta
|
||||
from lnbits.tasks import create_task, wait_for_paid_invoices
|
||||
|
|
@ -99,7 +99,7 @@ async def test_create_real_invoice(client, adminkey_headers_from, inkey_headers_
|
|||
raise FakeError()
|
||||
|
||||
task = create_task(wait_for_paid_invoices("test_create_invoice", on_paid)())
|
||||
pay_real_invoice(invoice["payment_request"])
|
||||
pay_real_invoice(invoice["bolt11"])
|
||||
|
||||
# wait for the task to exit
|
||||
with pytest.raises(FakeError):
|
||||
|
|
@ -143,7 +143,6 @@ async def test_pay_real_invoice_set_pending_and_check_state(
|
|||
payment = await get_standalone_payment(invoice["payment_hash"])
|
||||
assert payment
|
||||
assert payment.success
|
||||
assert payment.pending is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -160,28 +159,19 @@ async def test_pay_hold_invoice_check_pending(
|
|||
)
|
||||
)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
# get payment hash from the invoice
|
||||
invoice_obj = bolt11.decode(invoice["payment_request"])
|
||||
|
||||
payment_db = await get_standalone_payment(invoice_obj.payment_hash)
|
||||
|
||||
assert payment_db
|
||||
assert payment_db.pending is True
|
||||
|
||||
settle_invoice(preimage)
|
||||
|
||||
payment_db = await get_standalone_payment(invoice_obj.payment_hash)
|
||||
assert payment_db
|
||||
response = await task
|
||||
assert response.status_code < 300
|
||||
|
||||
# check if paid
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
payment_db_after_settlement = await get_standalone_payment(invoice_obj.payment_hash)
|
||||
|
||||
assert payment_db_after_settlement
|
||||
assert payment_db_after_settlement.pending is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -202,11 +192,6 @@ async def test_pay_hold_invoice_check_pending_and_fail(
|
|||
# get payment hash from the invoice
|
||||
invoice_obj = bolt11.decode(invoice["payment_request"])
|
||||
|
||||
payment_db = await get_standalone_payment(invoice_obj.payment_hash)
|
||||
|
||||
assert payment_db
|
||||
assert payment_db.pending is True
|
||||
|
||||
preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||
|
||||
# cancel the hodl invoice
|
||||
|
|
@ -221,7 +206,6 @@ async def test_pay_hold_invoice_check_pending_and_fail(
|
|||
# payment should be in database as failed
|
||||
payment_db_after_settlement = await get_standalone_payment(invoice_obj.payment_hash)
|
||||
assert payment_db_after_settlement
|
||||
assert payment_db_after_settlement.pending is False
|
||||
assert payment_db_after_settlement.failed is True
|
||||
|
||||
|
||||
|
|
@ -243,11 +227,6 @@ async def test_pay_hold_invoice_check_pending_and_fail_cancel_payment_task_in_me
|
|||
# get payment hash from the invoice
|
||||
invoice_obj = bolt11.decode(invoice["payment_request"])
|
||||
|
||||
payment_db = await get_standalone_payment(invoice_obj.payment_hash)
|
||||
|
||||
assert payment_db
|
||||
assert payment_db.pending is True
|
||||
|
||||
# cancel payment task, this simulates the client dropping the connection
|
||||
task.cancel()
|
||||
|
||||
|
|
@ -264,7 +243,7 @@ async def test_pay_hold_invoice_check_pending_and_fail_cancel_payment_task_in_me
|
|||
assert payment_db_after_settlement is not None
|
||||
|
||||
# payment is failed
|
||||
status = await payment_db.check_status()
|
||||
status = await payment_db_after_settlement.check_status()
|
||||
assert not status.paid
|
||||
assert status.failed
|
||||
|
||||
|
|
@ -307,16 +286,15 @@ async def test_receive_real_invoice_set_pending_and_check_state(
|
|||
assert payment_status["paid"]
|
||||
|
||||
assert payment
|
||||
assert payment.pending is False
|
||||
|
||||
# set the incoming invoice to pending
|
||||
await update_payment_details(payment.checking_id, status=PaymentState.PENDING)
|
||||
payment.status = PaymentState.PENDING
|
||||
await update_payment(payment)
|
||||
|
||||
payment_pending = await get_standalone_payment(
|
||||
invoice["payment_hash"], incoming=True
|
||||
)
|
||||
assert payment_pending
|
||||
assert payment_pending.pending is True
|
||||
assert payment_pending.success is False
|
||||
assert payment_pending.failed is False
|
||||
|
||||
|
|
@ -324,7 +302,7 @@ async def test_receive_real_invoice_set_pending_and_check_state(
|
|||
raise FakeError()
|
||||
|
||||
task = create_task(wait_for_paid_invoices("test_create_invoice", on_paid)())
|
||||
pay_real_invoice(invoice["payment_request"])
|
||||
pay_real_invoice(invoice["bolt11"])
|
||||
|
||||
with pytest.raises(FakeError):
|
||||
await task
|
||||
|
|
@ -349,7 +327,7 @@ async def test_check_fee_reserve(client, adminkey_headers_from):
|
|||
)
|
||||
assert response.status_code < 300
|
||||
invoice = response.json()
|
||||
payment_request = invoice["payment_request"]
|
||||
payment_request = invoice["bolt11"]
|
||||
|
||||
response = await client.get(
|
||||
f"/api/v1/payments/fee-reserve?invoice={payment_request}",
|
||||
|
|
|
|||
|
|
@ -2,45 +2,45 @@ import pytest
|
|||
from bolt11 import decode
|
||||
|
||||
from lnbits.core.services import (
|
||||
PaymentStatus,
|
||||
create_invoice,
|
||||
)
|
||||
from lnbits.wallets import get_funding_source
|
||||
from lnbits.wallets.base import PaymentStatus
|
||||
|
||||
description = "test create invoice"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_invoice(from_wallet):
|
||||
payment_hash, pr = await create_invoice(
|
||||
payment = await create_invoice(
|
||||
wallet_id=from_wallet.id,
|
||||
amount=1000,
|
||||
memo=description,
|
||||
)
|
||||
invoice = decode(pr)
|
||||
assert invoice.payment_hash == payment_hash
|
||||
invoice = decode(payment.bolt11)
|
||||
assert invoice.payment_hash == payment.payment_hash
|
||||
assert invoice.amount_msat == 1000000
|
||||
assert invoice.description == description
|
||||
|
||||
funding_source = get_funding_source()
|
||||
status = await funding_source.get_invoice_status(payment_hash)
|
||||
status = await funding_source.get_invoice_status(payment.payment_hash)
|
||||
assert isinstance(status, PaymentStatus)
|
||||
assert status.pending
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_internal_invoice(from_wallet):
|
||||
payment_hash, pr = await create_invoice(
|
||||
payment = await create_invoice(
|
||||
wallet_id=from_wallet.id, amount=1000, memo=description, internal=True
|
||||
)
|
||||
invoice = decode(pr)
|
||||
assert invoice.payment_hash == payment_hash
|
||||
invoice = decode(payment.bolt11)
|
||||
assert invoice.payment_hash == payment.payment_hash
|
||||
assert invoice.amount_msat == 1000000
|
||||
assert invoice.description == description
|
||||
|
||||
# Internal invoices are not on fundingsource. so we should get some kind of error
|
||||
# that the invoice is not found, but we get status pending
|
||||
funding_source = get_funding_source()
|
||||
status = await funding_source.get_invoice_status(payment_hash)
|
||||
status = await funding_source.get_invoice_status(payment.payment_hash)
|
||||
assert isinstance(status, PaymentStatus)
|
||||
assert status.pending
|
||||
|
|
|
|||
|
|
@ -1,27 +1,23 @@
|
|||
import pytest
|
||||
|
||||
from lnbits.core.crud import (
|
||||
get_standalone_payment,
|
||||
)
|
||||
from lnbits.core.models import PaymentState
|
||||
from lnbits.core.services import (
|
||||
PaymentError,
|
||||
pay_invoice,
|
||||
)
|
||||
from lnbits.exceptions import PaymentError
|
||||
|
||||
description = "test pay invoice"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_services_pay_invoice(to_wallet, real_invoice):
|
||||
payment_hash = await pay_invoice(
|
||||
payment = await pay_invoice(
|
||||
wallet_id=to_wallet.id,
|
||||
payment_request=real_invoice.get("bolt11"),
|
||||
description=description,
|
||||
)
|
||||
assert payment_hash
|
||||
payment = await get_standalone_payment(payment_hash)
|
||||
assert payment
|
||||
assert not payment.pending
|
||||
assert payment.status == PaymentState.SUCCESS
|
||||
assert payment.memo == description
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from pydantic import parse_obj_as
|
|||
|
||||
from lnbits import bolt11
|
||||
from lnbits.nodes.base import ChannelPoint, ChannelState, NodeChannel
|
||||
from tests.conftest import pytest_asyncio, settings
|
||||
from tests.conftest import pytest_asyncio
|
||||
|
||||
from ..helpers import (
|
||||
funding_source,
|
||||
|
|
@ -25,7 +25,7 @@ pytestmark = pytest.mark.skipif(
|
|||
|
||||
|
||||
@pytest_asyncio.fixture()
|
||||
async def node_client(client, from_super_user):
|
||||
async def node_client(client, from_super_user, settings):
|
||||
settings.lnbits_node_ui = True
|
||||
settings.lnbits_public_node_ui = False
|
||||
settings.lnbits_node_ui_transactions = True
|
||||
|
|
@ -37,14 +37,14 @@ async def node_client(client, from_super_user):
|
|||
|
||||
|
||||
@pytest_asyncio.fixture()
|
||||
async def public_node_client(node_client):
|
||||
async def public_node_client(node_client, settings):
|
||||
settings.lnbits_public_node_ui = True
|
||||
yield node_client
|
||||
settings.lnbits_public_node_ui = False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_node_info_not_found(client, from_super_user):
|
||||
async def test_node_info_not_found(client, from_super_user, settings):
|
||||
settings.lnbits_node_ui = False
|
||||
response = await client.get("/node/api/v1/info", params={"usr": from_super_user.id})
|
||||
assert response.status_code == HTTPStatus.SERVICE_UNAVAILABLE
|
||||
|
|
|
|||
|
|
@ -1,28 +1,72 @@
|
|||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from lnbits.helpers import (
|
||||
from lnbits.db import (
|
||||
dict_to_model,
|
||||
insert_query,
|
||||
model_to_dict,
|
||||
update_query,
|
||||
)
|
||||
from tests.helpers import DbTestModel
|
||||
from tests.helpers import DbTestModel, DbTestModel2, DbTestModel3
|
||||
|
||||
test = DbTestModel(id=1, name="test", value="yes")
|
||||
test_data = DbTestModel3(
|
||||
id=1,
|
||||
user="userid",
|
||||
child=DbTestModel2(
|
||||
id=2,
|
||||
label="test",
|
||||
description="mydesc",
|
||||
child=DbTestModel(id=3, name="myname", value="myvalue"),
|
||||
),
|
||||
active=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_helpers_insert_query():
|
||||
q = insert_query("test_helpers_query", test)
|
||||
assert (
|
||||
q == "INSERT INTO test_helpers_query (id, name, value) "
|
||||
"VALUES (:id, :name, :value)"
|
||||
q = insert_query("test_helpers_query", test_data)
|
||||
assert q == (
|
||||
"""INSERT INTO test_helpers_query ("id", "user", "child", "active") """
|
||||
"VALUES (:id, :user, :child, :active)"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_helpers_update_query():
|
||||
q = update_query("test_helpers_query", test)
|
||||
assert (
|
||||
q == "UPDATE test_helpers_query "
|
||||
"SET id = :id, name = :name, value = :value "
|
||||
"WHERE id = :id"
|
||||
q = update_query("test_helpers_query", test_data)
|
||||
assert q == (
|
||||
"""UPDATE test_helpers_query SET "id" = :id, "user" = """
|
||||
""":user, "child" = :child, "active" = :active WHERE id = :id"""
|
||||
)
|
||||
|
||||
|
||||
child_json = json.dumps(
|
||||
{
|
||||
"id": 2,
|
||||
"label": "test",
|
||||
"description": "mydesc",
|
||||
"child": {"id": 3, "name": "myname", "value": "myvalue"},
|
||||
}
|
||||
)
|
||||
test_dict = {"id": 1, "user": "userid", "child": child_json, "active": True}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_helpers_model_to_dict():
|
||||
d = model_to_dict(test_data)
|
||||
assert d.get("id") == test_data.id
|
||||
assert d.get("active") == test_data.active
|
||||
assert d.get("child") == child_json
|
||||
assert d.get("user") == test_data.user
|
||||
assert d == test_dict
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_helpers_dict_to_model():
|
||||
m = dict_to_model(test_dict, DbTestModel3)
|
||||
assert m == test_data
|
||||
assert type(m) is DbTestModel3
|
||||
assert m.active is True
|
||||
assert type(m.child) is DbTestModel2
|
||||
assert type(m.child.child) is DbTestModel
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from lnbits.core.crud import get_standalone_payment, get_wallet
|
|||
from lnbits.core.models import Payment, PaymentState, Wallet
|
||||
from lnbits.core.services import create_invoice, pay_invoice
|
||||
from lnbits.exceptions import PaymentError
|
||||
from lnbits.settings import settings
|
||||
from lnbits.settings import Settings
|
||||
from lnbits.tasks import (
|
||||
create_permanent_task,
|
||||
internal_invoice_listener,
|
||||
|
|
@ -49,43 +49,40 @@ async def test_amountless_invoice(to_wallet: Wallet):
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bad_wallet_id(to_wallet: Wallet):
|
||||
_, payment_request = await create_invoice(
|
||||
wallet_id=to_wallet.id, amount=31, memo="Bad Wallet"
|
||||
)
|
||||
with pytest.raises(AssertionError, match="invalid wallet_id"):
|
||||
payment = await create_invoice(wallet_id=to_wallet.id, amount=31, memo="Bad Wallet")
|
||||
bad_wallet_id = to_wallet.id[::-1]
|
||||
with pytest.raises(
|
||||
PaymentError, match=f"Could not fetch wallet '{bad_wallet_id}'."
|
||||
):
|
||||
await pay_invoice(
|
||||
wallet_id=to_wallet.id[::-1],
|
||||
payment_request=payment_request,
|
||||
wallet_id=bad_wallet_id,
|
||||
payment_request=payment.bolt11,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_payment_limit(to_wallet: Wallet):
|
||||
_, payment_request = await create_invoice(
|
||||
wallet_id=to_wallet.id, amount=101, memo=""
|
||||
)
|
||||
payment = await create_invoice(wallet_id=to_wallet.id, amount=101, memo="")
|
||||
with pytest.raises(PaymentError, match="Amount in invoice is too high."):
|
||||
|
||||
await pay_invoice(
|
||||
wallet_id=to_wallet.id,
|
||||
max_sat=100,
|
||||
payment_request=payment_request,
|
||||
payment_request=payment.bolt11,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pay_twice(to_wallet: Wallet):
|
||||
_, payment_request = await create_invoice(
|
||||
wallet_id=to_wallet.id, amount=3, memo="Twice"
|
||||
)
|
||||
payment = await create_invoice(wallet_id=to_wallet.id, amount=3, memo="Twice")
|
||||
await pay_invoice(
|
||||
wallet_id=to_wallet.id,
|
||||
payment_request=payment_request,
|
||||
payment_request=payment.bolt11,
|
||||
)
|
||||
with pytest.raises(PaymentError, match="Internal invoice already paid."):
|
||||
await pay_invoice(
|
||||
wallet_id=to_wallet.id,
|
||||
payment_request=payment_request,
|
||||
payment_request=payment.bolt11,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -106,15 +103,13 @@ async def test_fake_wallet_pay_external(
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invoice_changed(to_wallet: Wallet):
|
||||
_, payment_request = await create_invoice(
|
||||
wallet_id=to_wallet.id, amount=21, memo="original"
|
||||
)
|
||||
payment = await create_invoice(wallet_id=to_wallet.id, amount=21, memo="original")
|
||||
|
||||
invoice = bolt11_decode(payment_request)
|
||||
invoice = bolt11_decode(payment.bolt11)
|
||||
invoice.amount_msat = MilliSatoshi(12000)
|
||||
payment_request = bolt11_encode(invoice)
|
||||
|
||||
with pytest.raises(PaymentError, match="Invalid invoice."):
|
||||
with pytest.raises(PaymentError, match="Invalid invoice. Bolt11 changed."):
|
||||
await pay_invoice(
|
||||
wallet_id=to_wallet.id,
|
||||
payment_request=payment_request,
|
||||
|
|
@ -132,24 +127,20 @@ async def test_invoice_changed(to_wallet: Wallet):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pay_for_extension(to_wallet: Wallet):
|
||||
_, payment_request = await create_invoice(
|
||||
wallet_id=to_wallet.id, amount=3, memo="Allowed"
|
||||
)
|
||||
async def test_pay_for_extension(to_wallet: Wallet, settings: Settings):
|
||||
payment = await create_invoice(wallet_id=to_wallet.id, amount=3, memo="Allowed")
|
||||
await pay_invoice(
|
||||
wallet_id=to_wallet.id, payment_request=payment_request, extra={"tag": "lnurlp"}
|
||||
)
|
||||
_, payment_request = await create_invoice(
|
||||
wallet_id=to_wallet.id, amount=3, memo="Not Allowed"
|
||||
wallet_id=to_wallet.id, payment_request=payment.bolt11, tag="lnurlp"
|
||||
)
|
||||
payment = await create_invoice(wallet_id=to_wallet.id, amount=3, memo="Not Allowed")
|
||||
settings.lnbits_admin_extensions = ["lnurlp"]
|
||||
with pytest.raises(
|
||||
PaymentError, match="User not authorized for extension 'lnurlp'."
|
||||
):
|
||||
await pay_invoice(
|
||||
wallet_id=to_wallet.id,
|
||||
payment_request=payment_request,
|
||||
extra={"tag": "lnurlp"},
|
||||
payment_request=payment.bolt11,
|
||||
tag="lnurlp",
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -161,21 +152,19 @@ async def test_notification_for_internal_payment(to_wallet: Wallet):
|
|||
invoice_queue: asyncio.Queue = asyncio.Queue()
|
||||
register_invoice_listener(invoice_queue, test_name)
|
||||
|
||||
_, payment_request = await create_invoice(
|
||||
wallet_id=to_wallet.id, amount=123, memo=test_name
|
||||
)
|
||||
payment = await create_invoice(wallet_id=to_wallet.id, amount=123, memo=test_name)
|
||||
await pay_invoice(
|
||||
wallet_id=to_wallet.id, payment_request=payment_request, extra={"tag": "lnurlp"}
|
||||
wallet_id=to_wallet.id, payment_request=payment.bolt11, extra={"tag": "lnurlp"}
|
||||
)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
while True:
|
||||
payment: Payment = invoice_queue.get_nowait() # raises if queue empty
|
||||
assert payment
|
||||
if payment.memo == test_name:
|
||||
assert payment.status == PaymentState.SUCCESS.value
|
||||
assert payment.bolt11 == payment_request
|
||||
assert payment.amount == 123_000
|
||||
_payment: Payment = invoice_queue.get_nowait() # raises if queue empty
|
||||
assert _payment
|
||||
if _payment.memo == test_name:
|
||||
assert _payment.status == PaymentState.SUCCESS.value
|
||||
assert _payment.bolt11 == payment.bolt11
|
||||
assert _payment.amount == 123_000
|
||||
break # we found our payment, success
|
||||
|
||||
|
||||
|
|
@ -216,7 +205,7 @@ async def test_retry_failed_invoice(
|
|||
assert external_invoice.payment_request
|
||||
|
||||
ws_notification = mocker.patch(
|
||||
"lnbits.core.services.send_payment_notification",
|
||||
"lnbits.core.services.payments.send_payment_notification",
|
||||
AsyncMock(return_value=None),
|
||||
)
|
||||
|
||||
|
|
@ -293,24 +282,24 @@ async def test_pay_external_invoice_pending(
|
|||
AsyncMock(return_value=payment_reponse_pending),
|
||||
)
|
||||
ws_notification = mocker.patch(
|
||||
"lnbits.core.services.send_payment_notification",
|
||||
"lnbits.core.services.payments.send_payment_notification",
|
||||
AsyncMock(return_value=None),
|
||||
)
|
||||
wallet = await get_wallet(from_wallet.id)
|
||||
assert wallet
|
||||
balance_before = wallet.balance
|
||||
payment_hash = await pay_invoice(
|
||||
payment = await pay_invoice(
|
||||
wallet_id=from_wallet.id,
|
||||
payment_request=external_invoice.payment_request,
|
||||
)
|
||||
|
||||
payment = await get_standalone_payment(payment_hash)
|
||||
assert payment
|
||||
assert payment.status == PaymentState.PENDING.value
|
||||
assert payment.checking_id == payment_hash
|
||||
assert payment.amount == -2103_000
|
||||
assert payment.bolt11 == external_invoice.payment_request
|
||||
assert payment.preimage == preimage
|
||||
_payment = await get_standalone_payment(payment.payment_hash)
|
||||
assert _payment
|
||||
assert _payment.status == PaymentState.PENDING.value
|
||||
assert _payment.checking_id == payment.payment_hash
|
||||
assert _payment.amount == -2103_000
|
||||
assert _payment.bolt11 == external_invoice.payment_request
|
||||
assert _payment.preimage == preimage
|
||||
|
||||
wallet = await get_wallet(from_wallet.id)
|
||||
assert wallet
|
||||
|
|
@ -339,7 +328,7 @@ async def test_retry_pay_external_invoice_pending(
|
|||
AsyncMock(return_value=payment_reponse_pending),
|
||||
)
|
||||
ws_notification = mocker.patch(
|
||||
"lnbits.core.services.send_payment_notification",
|
||||
"lnbits.core.services.payments.send_payment_notification",
|
||||
AsyncMock(return_value=None),
|
||||
)
|
||||
wallet = await get_wallet(from_wallet.id)
|
||||
|
|
@ -384,24 +373,24 @@ async def test_pay_external_invoice_success(
|
|||
AsyncMock(return_value=payment_reponse_pending),
|
||||
)
|
||||
ws_notification = mocker.patch(
|
||||
"lnbits.core.services.send_payment_notification",
|
||||
"lnbits.core.services.payments.send_payment_notification",
|
||||
AsyncMock(return_value=None),
|
||||
)
|
||||
wallet = await get_wallet(from_wallet.id)
|
||||
assert wallet
|
||||
balance_before = wallet.balance
|
||||
payment_hash = await pay_invoice(
|
||||
payment = await pay_invoice(
|
||||
wallet_id=from_wallet.id,
|
||||
payment_request=external_invoice.payment_request,
|
||||
)
|
||||
|
||||
payment = await get_standalone_payment(payment_hash)
|
||||
assert payment
|
||||
assert payment.status == PaymentState.SUCCESS.value
|
||||
assert payment.checking_id == payment_hash
|
||||
assert payment.amount == -2104_000
|
||||
assert payment.bolt11 == external_invoice.payment_request
|
||||
assert payment.preimage == preimage
|
||||
_payment = await get_standalone_payment(payment.payment_hash)
|
||||
assert _payment
|
||||
assert _payment.status == PaymentState.SUCCESS.value
|
||||
assert _payment.checking_id == payment.payment_hash
|
||||
assert _payment.amount == -2104_000
|
||||
assert _payment.bolt11 == external_invoice.payment_request
|
||||
assert _payment.preimage == preimage
|
||||
|
||||
wallet = await get_wallet(from_wallet.id)
|
||||
assert wallet
|
||||
|
|
@ -430,7 +419,7 @@ async def test_retry_pay_success(
|
|||
AsyncMock(return_value=payment_reponse_pending),
|
||||
)
|
||||
ws_notification = mocker.patch(
|
||||
"lnbits.core.services.send_payment_notification",
|
||||
"lnbits.core.services.payments.send_payment_notification",
|
||||
AsyncMock(return_value=None),
|
||||
)
|
||||
wallet = await get_wallet(from_wallet.id)
|
||||
|
|
@ -465,15 +454,15 @@ async def test_pay_external_invoice_success_bad_checking_id(
|
|||
external_invoice = await external_funding_source.create_invoice(invoice_amount)
|
||||
assert external_invoice.payment_request
|
||||
assert external_invoice.checking_id
|
||||
bad_checking_id = external_invoice.checking_id[::-1]
|
||||
bad_checking_id = f"bad_{external_invoice.checking_id}"
|
||||
|
||||
preimage = "0000000000000000000000000000000000000000000000000000000000002108"
|
||||
payment_reponse_pending = PaymentResponse(
|
||||
payment_reponse_success = PaymentResponse(
|
||||
ok=True, checking_id=bad_checking_id, preimage=preimage
|
||||
)
|
||||
mocker.patch(
|
||||
"lnbits.wallets.FakeWallet.pay_invoice",
|
||||
AsyncMock(return_value=payment_reponse_pending),
|
||||
AsyncMock(return_value=payment_reponse_success),
|
||||
)
|
||||
|
||||
await pay_invoice(
|
||||
|
|
@ -519,10 +508,7 @@ async def test_no_checking_id(
|
|||
assert payment.checking_id == external_invoice.checking_id
|
||||
assert payment.payment_hash == external_invoice.checking_id
|
||||
assert payment.amount == -2110_000
|
||||
assert (
|
||||
payment.preimage
|
||||
== "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
)
|
||||
assert payment.preimage is None
|
||||
assert payment.status == PaymentState.PENDING.value
|
||||
|
||||
|
||||
|
|
@ -532,6 +518,7 @@ async def test_service_fee(
|
|||
to_wallet: Wallet,
|
||||
mocker: MockerFixture,
|
||||
external_funding_source: FakeWallet,
|
||||
settings: Settings,
|
||||
):
|
||||
invoice_amount = 2112
|
||||
external_invoice = await external_funding_source.create_invoice(invoice_amount)
|
||||
|
|
@ -550,27 +537,26 @@ async def test_service_fee(
|
|||
settings.lnbits_service_fee_wallet = to_wallet.id
|
||||
settings.lnbits_service_fee = 20
|
||||
|
||||
payment_hash = await pay_invoice(
|
||||
payment = await pay_invoice(
|
||||
wallet_id=from_wallet.id,
|
||||
payment_request=external_invoice.payment_request,
|
||||
)
|
||||
|
||||
payment = await get_standalone_payment(payment_hash)
|
||||
assert payment
|
||||
assert payment.status == PaymentState.SUCCESS.value
|
||||
assert payment.checking_id == payment_hash
|
||||
assert payment.amount == -2112_000
|
||||
assert payment.fee == -422_400
|
||||
assert payment.bolt11 == external_invoice.payment_request
|
||||
assert payment.preimage == preimage
|
||||
_payment = await get_standalone_payment(payment.payment_hash)
|
||||
assert _payment
|
||||
assert _payment.status == PaymentState.SUCCESS.value
|
||||
assert _payment.checking_id == payment.payment_hash
|
||||
assert _payment.amount == -2112_000
|
||||
assert _payment.fee == -422_400
|
||||
assert _payment.bolt11 == external_invoice.payment_request
|
||||
assert _payment.preimage == preimage
|
||||
|
||||
service_fee_payment = await get_standalone_payment(f"service_fee_{payment_hash}")
|
||||
service_fee_payment = await get_standalone_payment(
|
||||
f"service_fee_{payment.payment_hash}"
|
||||
)
|
||||
assert service_fee_payment
|
||||
assert service_fee_payment.status == PaymentState.SUCCESS.value
|
||||
assert service_fee_payment.checking_id == f"service_fee_{payment_hash}"
|
||||
assert service_fee_payment.checking_id == f"service_fee_{payment.payment_hash}"
|
||||
assert service_fee_payment.amount == 422_400
|
||||
assert service_fee_payment.bolt11 == external_invoice.payment_request
|
||||
assert (
|
||||
service_fee_payment.preimage
|
||||
== "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
)
|
||||
assert service_fee_payment.preimage is None
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from lnbits.core.services import (
|
|||
fee_reserve_total,
|
||||
service_fee,
|
||||
)
|
||||
from lnbits.settings import settings
|
||||
from lnbits.settings import Settings
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -15,7 +15,7 @@ async def test_fee_reserve_internal():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fee_reserve_min():
|
||||
async def test_fee_reserve_min(settings: Settings):
|
||||
settings.lnbits_reserve_fee_percent = 2
|
||||
settings.lnbits_reserve_fee_min = 500
|
||||
fee = fee_reserve(10000)
|
||||
|
|
@ -23,7 +23,7 @@ async def test_fee_reserve_min():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fee_reserve_percent():
|
||||
async def test_fee_reserve_percent(settings: Settings):
|
||||
settings.lnbits_reserve_fee_percent = 1
|
||||
settings.lnbits_reserve_fee_min = 100
|
||||
fee = fee_reserve(100000)
|
||||
|
|
@ -31,14 +31,14 @@ async def test_fee_reserve_percent():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_service_fee_no_wallet():
|
||||
async def test_service_fee_no_wallet(settings: Settings):
|
||||
settings.lnbits_service_fee_wallet = ""
|
||||
fee = service_fee(10000)
|
||||
assert fee == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_service_fee_internal():
|
||||
async def test_service_fee_internal(settings: Settings):
|
||||
settings.lnbits_service_fee_wallet = "wallet_id"
|
||||
settings.lnbits_service_fee_ignore_internal = True
|
||||
fee = service_fee(10000, internal=True)
|
||||
|
|
@ -46,7 +46,7 @@ async def test_service_fee_internal():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_service_fee():
|
||||
async def test_service_fee(settings: Settings):
|
||||
settings.lnbits_service_fee_wallet = "wallet_id"
|
||||
settings.lnbits_service_fee = 2
|
||||
fee = service_fee(10000)
|
||||
|
|
@ -54,7 +54,7 @@ async def test_service_fee():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_service_fee_max():
|
||||
async def test_service_fee_max(settings: Settings):
|
||||
settings.lnbits_service_fee_wallet = "wallet_id"
|
||||
settings.lnbits_service_fee = 2
|
||||
settings.lnbits_service_fee_max = 199
|
||||
|
|
@ -63,7 +63,7 @@ async def test_service_fee_max():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fee_reserve_total():
|
||||
async def test_fee_reserve_total(settings: Settings):
|
||||
settings.lnbits_reserve_fee_percent = 1
|
||||
settings.lnbits_reserve_fee_min = 100
|
||||
settings.lnbits_service_fee = 2
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import pytest
|
||||
|
||||
from lnbits.core.services import check_wallet_daily_withdraw_limit
|
||||
from lnbits.settings import settings
|
||||
from lnbits.core.services.payments import check_wallet_daily_withdraw_limit
|
||||
from lnbits.settings import Settings
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_no_wallet_limit():
|
||||
async def test_no_wallet_limit(settings: Settings):
|
||||
settings.lnbits_wallet_limit_daily_max_withdraw = 0
|
||||
result = await check_wallet_daily_withdraw_limit(
|
||||
conn=None, wallet_id="333333", amount_msat=0
|
||||
|
|
@ -15,7 +15,7 @@ async def test_no_wallet_limit():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wallet_limit_but_no_payments():
|
||||
async def test_wallet_limit_but_no_payments(settings: Settings):
|
||||
settings.lnbits_wallet_limit_daily_max_withdraw = 5
|
||||
result = await check_wallet_daily_withdraw_limit(
|
||||
conn=None, wallet_id="333333", amount_msat=0
|
||||
|
|
@ -25,7 +25,7 @@ async def test_wallet_limit_but_no_payments():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_no_wallet_spend_allowed():
|
||||
async def test_no_wallet_spend_allowed(settings: Settings):
|
||||
settings.lnbits_wallet_limit_daily_max_withdraw = -1
|
||||
|
||||
with pytest.raises(
|
||||
|
|
|
|||
|
|
@ -617,7 +617,7 @@
|
|||
"response_type": "json",
|
||||
"response": {
|
||||
"checking_id": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
||||
"payment_request": "lnbc5550n1pnq9jg3sp52rvwstvjcypjsaenzdh0h30jazvzsf8aaye0julprtth9kysxtuspp5e5s3z7felv4t9zrcc6wpn7ehvjl5yzewanzl5crljdl3jgeffyhqdq2f38xy6t5wvxqzjccqpjrzjq0yzeq76ney45hmjlnlpvu0nakzy2g35hqh0dujq8ujdpr2e42pf2rrs6vqpgcsqqqqqqqqqqqqqqeqqyg9qxpqysgqwftcx89k5pp28435pgxfl2vx3ksemzxccppw2j9yjn0ngr6ed7wj8ztc0d5kmt2mvzdlcgrludhz7jncd5l5l9w820hc4clpwhtqj3gq62g66n"
|
||||
"bolt11": "lnbc5550n1pnq9jg3sp52rvwstvjcypjsaenzdh0h30jazvzsf8aaye0julprtth9kysxtuspp5e5s3z7felv4t9zrcc6wpn7ehvjl5yzewanzl5crljdl3jgeffyhqdq2f38xy6t5wvxqzjccqpjrzjq0yzeq76ney45hmjlnlpvu0nakzy2g35hqh0dujq8ujdpr2e42pf2rrs6vqpgcsqqqqqqqqqqqqqqeqqyg9qxpqysgqwftcx89k5pp28435pgxfl2vx3ksemzxccppw2j9yjn0ngr6ed7wj8ztc0d5kmt2mvzdlcgrludhz7jncd5l5l9w820hc4clpwhtqj3gq62g66n"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -825,7 +825,7 @@
|
|||
},
|
||||
"response_type": "json",
|
||||
"response": {
|
||||
"payment_request": "lnbc5550n1pnq9jg3sp52rvwstvjcypjsaenzdh0h30jazvzsf8aaye0julprtth9kysxtuspp5e5s3z7felv4t9zrcc6wpn7ehvjl5yzewanzl5crljdl3jgeffyhqdq2f38xy6t5wvxqzjccqpjrzjq0yzeq76ney45hmjlnlpvu0nakzy2g35hqh0dujq8ujdpr2e42pf2rrs6vqpgcsqqqqqqqqqqqqqqeqqyg9qxpqysgqwftcx89k5pp28435pgxfl2vx3ksemzxccppw2j9yjn0ngr6ed7wj8ztc0d5kmt2mvzdlcgrludhz7jncd5l5l9w820hc4clpwhtqj3gq62g66n"
|
||||
"bolt11": "lnbc5550n1pnq9jg3sp52rvwstvjcypjsaenzdh0h30jazvzsf8aaye0julprtth9kysxtuspp5e5s3z7felv4t9zrcc6wpn7ehvjl5yzewanzl5crljdl3jgeffyhqdq2f38xy6t5wvxqzjccqpjrzjq0yzeq76ney45hmjlnlpvu0nakzy2g35hqh0dujq8ujdpr2e42pf2rrs6vqpgcsqqqqqqqqqqqqqqeqqyg9qxpqysgqwftcx89k5pp28435pgxfl2vx3ksemzxccppw2j9yjn0ngr6ed7wj8ztc0d5kmt2mvzdlcgrludhz7jncd5l5l9w820hc4clpwhtqj3gq62g66n"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue