[feat] Payment labels (#3537)
This commit is contained in:
parent
3ccefb70fa
commit
7c7a04da9d
26 changed files with 930 additions and 39 deletions
|
|
@ -1,12 +1,16 @@
|
|||
import hashlib
|
||||
from json import JSONDecodeError
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
import shortuuid
|
||||
from pytest_mock.plugin import MockerFixture
|
||||
|
||||
from lnbits import bolt11
|
||||
from lnbits.core.models import CreateInvoice, Payment
|
||||
from lnbits.core.models.users import Account, UserExtra, UserLabel
|
||||
from lnbits.core.services.users import create_user_account
|
||||
from lnbits.core.views.payment_api import api_payment
|
||||
from lnbits.fiat.base import FiatInvoiceResponse
|
||||
from lnbits.settings import Settings
|
||||
|
|
@ -789,3 +793,176 @@ async def test_api_payments_pay_lnurl(client, adminkey_headers_from):
|
|||
)
|
||||
assert response.status_code == 400
|
||||
assert "value_error.url.scheme" in response.json()["detail"]
|
||||
|
||||
|
||||
################################ Labels ################################
|
||||
@pytest.mark.anyio
|
||||
async def test_api_search_payment_labels(client):
|
||||
tiny_id = shortuuid.uuid()[:8]
|
||||
user = await create_user_account(
|
||||
Account(
|
||||
id=uuid4().hex,
|
||||
username=f"u{tiny_id}",
|
||||
extra=UserExtra(
|
||||
labels=[
|
||||
UserLabel(name="label A", color="#FF0000"),
|
||||
UserLabel(name="label B", color="#00FF00"),
|
||||
]
|
||||
),
|
||||
)
|
||||
)
|
||||
assert len(user.extra.labels) == 2
|
||||
adminkey = user.wallets[0].adminkey
|
||||
payments_headers = {
|
||||
"X-Api-Key": adminkey,
|
||||
"Content-type": "application/json",
|
||||
}
|
||||
|
||||
payment_count = 10
|
||||
await _create_some_payments(payment_count, client, payments_headers)
|
||||
|
||||
# search payments by label A
|
||||
response = await client.get(
|
||||
"/api/v1/payments/paginated",
|
||||
params={"labels[every]": ["label A"]},
|
||||
headers=payments_headers,
|
||||
)
|
||||
assert response.is_success
|
||||
data = response.json()
|
||||
assert data["total"] == payment_count // 2
|
||||
for payment in data["data"]:
|
||||
assert "label A" in payment["labels"]
|
||||
|
||||
# search payments by label B
|
||||
response = await client.get(
|
||||
"/api/v1/payments/paginated",
|
||||
params={"labels[every]": ["label B"]},
|
||||
headers=payments_headers,
|
||||
)
|
||||
assert response.is_success
|
||||
data = response.json()
|
||||
assert data["total"] == payment_count // 3
|
||||
for payment in data["data"]:
|
||||
assert "label B" in payment["labels"]
|
||||
|
||||
# search payments by label C
|
||||
response = await client.get(
|
||||
"/api/v1/payments/paginated",
|
||||
params={"labels[every]": ["label C"]},
|
||||
headers=payments_headers,
|
||||
)
|
||||
assert response.is_success
|
||||
data = response.json()
|
||||
assert data["total"] == payment_count // 5
|
||||
for payment in data["data"]:
|
||||
assert "label C" in payment["labels"]
|
||||
|
||||
# search payments by label A and B
|
||||
response = await client.get(
|
||||
"/api/v1/payments/paginated",
|
||||
params={"labels[every]": ["label A", "label B"]},
|
||||
headers=payments_headers,
|
||||
)
|
||||
assert response.is_success
|
||||
data = response.json()
|
||||
|
||||
assert data["total"] == payment_count // 6
|
||||
for payment in data["data"]:
|
||||
assert "label A" in payment["labels"]
|
||||
assert "label B" in payment["labels"]
|
||||
|
||||
# search payments for random label D (no payments)
|
||||
response = await client.get(
|
||||
"/api/v1/payments/paginated",
|
||||
params={"labels[every]": ["label D"]},
|
||||
headers=payments_headers,
|
||||
)
|
||||
assert response.is_success
|
||||
data = response.json()
|
||||
|
||||
assert data["total"] == 0
|
||||
|
||||
# search payments with no label filter (all payments)
|
||||
response = await client.get(
|
||||
"/api/v1/payments/paginated",
|
||||
params={"labels[every]": []},
|
||||
headers=payments_headers,
|
||||
)
|
||||
assert response.is_success
|
||||
all_payments = response.json()
|
||||
|
||||
assert all_payments["total"] == payment_count
|
||||
|
||||
no_label_a_payment = next(
|
||||
(
|
||||
payment
|
||||
for payment in all_payments["data"]
|
||||
if "label A" not in payment["labels"]
|
||||
),
|
||||
None,
|
||||
)
|
||||
assert no_label_a_payment is not None
|
||||
payment_hash = no_label_a_payment["payment_hash"]
|
||||
response = await client.put(
|
||||
f"/api/v1/payments/{payment_hash}/labels",
|
||||
headers=payments_headers,
|
||||
json={"labels": ["label A"]},
|
||||
)
|
||||
|
||||
# search payments by label A after update
|
||||
response = await client.get(
|
||||
"/api/v1/payments/paginated",
|
||||
params={"labels[every]": ["label A"]},
|
||||
headers=payments_headers,
|
||||
)
|
||||
assert response.is_success
|
||||
data = response.json()
|
||||
assert data["total"] == payment_count // 2 + 1 # one more after update
|
||||
for payment in data["data"]:
|
||||
assert "label A" in payment["labels"]
|
||||
|
||||
# remove label A from all payments
|
||||
for payment in all_payments["data"]:
|
||||
payment_hash = payment["payment_hash"]
|
||||
response = await client.put(
|
||||
f"/api/v1/payments/{payment_hash}/labels",
|
||||
headers=payments_headers,
|
||||
json={"labels": []},
|
||||
)
|
||||
|
||||
# search payments by label A (none should have it now)
|
||||
response = await client.get(
|
||||
"/api/v1/payments/paginated",
|
||||
params={"labels[every]": ["label A"]},
|
||||
headers=payments_headers,
|
||||
)
|
||||
assert response.is_success
|
||||
data = response.json()
|
||||
assert data["total"] == 0
|
||||
|
||||
|
||||
async def _create_some_payments(payment_count: int, client, payments_headers):
|
||||
payment_count = 10
|
||||
for index in range(1, payment_count + 1):
|
||||
labels = []
|
||||
if index % 2 == 0:
|
||||
labels.append("label A")
|
||||
if index % 3 == 0:
|
||||
labels.append("label B")
|
||||
if index % 5 == 0:
|
||||
# User does not have this label, but will be added to the payment.
|
||||
labels.append("label C")
|
||||
response = await client.post(
|
||||
"/api/v1/payments",
|
||||
headers=payments_headers,
|
||||
json={
|
||||
"out": False,
|
||||
"amount": 1000 + index,
|
||||
"memo": f"payment {index}",
|
||||
"labels": labels,
|
||||
},
|
||||
)
|
||||
assert response.is_success
|
||||
data = response.json()
|
||||
assert data["labels"] == labels
|
||||
return payment_count
|
||||
|
|
|
|||
|
|
@ -24,7 +24,9 @@ from lnbits.core.models.users import (
|
|||
EndpointAccess,
|
||||
LoginUsr,
|
||||
UpdateAccessControlList,
|
||||
UpdateUser,
|
||||
UserAcls,
|
||||
UserLabel,
|
||||
)
|
||||
from lnbits.core.services.users import create_user_account
|
||||
from lnbits.core.views.user_api import api_users_reset_password
|
||||
|
|
@ -1999,3 +2001,48 @@ async def test_api_delete_user_api_token_missing_token_id(
|
|||
json=delete_token_request.dict(),
|
||||
)
|
||||
assert response.status_code == 200, "Does noting if token not found."
|
||||
|
||||
|
||||
################################ Labels ################################
|
||||
@pytest.mark.anyio
|
||||
async def test_api_update_user_labels(http_client: AsyncClient):
|
||||
tiny_id = shortuuid.uuid()[:8]
|
||||
user = await create_user_account()
|
||||
assert user.extra.labels == []
|
||||
|
||||
user.extra.labels = [
|
||||
UserLabel(name="label 01", color="#FF0000"),
|
||||
UserLabel(name="label 02", color="#00FF00"),
|
||||
]
|
||||
data = UpdateUser(user_id=user.id, username=f"u{tiny_id}", extra=user.extra)
|
||||
assert data.extra
|
||||
response = await http_client.put(
|
||||
"/api/v1/auth/update?usr=" + user.id, json=data.dict()
|
||||
)
|
||||
assert response.status_code == 200
|
||||
user_data = response.json()
|
||||
assert len(user_data["extra"]["labels"]) == 2
|
||||
assert user_data["extra"]["labels"][0]["name"] == "label 01"
|
||||
assert user_data["extra"]["labels"][0]["color"] == "#FF0000"
|
||||
assert user_data["extra"]["labels"][1]["name"] == "label 02"
|
||||
assert user_data["extra"]["labels"][1]["color"] == "#00FF00"
|
||||
|
||||
data.extra.labels = []
|
||||
response = await http_client.put(
|
||||
"/api/v1/auth/update?usr=" + user.id, json=data.dict()
|
||||
)
|
||||
assert response.status_code == 200
|
||||
user_data = response.json()
|
||||
assert len(user_data["extra"]["labels"]) == 0
|
||||
|
||||
json_data = data.dict()
|
||||
json_data["extra"] = {"labels": [{"name": "label + 01", "color": "#FF0000"}]}
|
||||
response = await http_client.put(
|
||||
"/api/v1/auth/update?usr=" + user.id, json=json_data
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
data = response.json()
|
||||
assert (
|
||||
"""string does not match regex "([A-Za-z0-9 ._-]{1,100}$)""" in data["detail"]
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue