Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
5b3416d
test: initial commit
motorina0 Mar 26, 2024
47f8a48
chore: code format
motorina0 Mar 26, 2024
c94111a
fix: comment out bad `status.pending` (to be fixed in core)
motorina0 Mar 26, 2024
5544ec7
fix: 404 tests
motorina0 Mar 26, 2024
824cca1
test: extract first `create_invoice` test
motorina0 Mar 26, 2024
5196457
chore: reminder
motorina0 Mar 26, 2024
52f5ea5
add: error test
motorina0 Mar 26, 2024
6264396
chore: experiment
motorina0 Mar 27, 2024
bf09344
feat: adapt parsing
motorina0 Mar 28, 2024
8024bf8
refactor: data structure
motorina0 Mar 28, 2024
fa64312
fix: some tests
motorina0 Mar 28, 2024
1ed6e88
fix: make response uniform
motorina0 Mar 28, 2024
f35c8a8
fix: test data
motorina0 Mar 28, 2024
654a15f
chore: clean-up
motorina0 Mar 28, 2024
73f7f3b
fix: uniform responses
motorina0 Mar 28, 2024
c21d69a
fix: user agent
motorina0 Mar 28, 2024
95affec
fix: user agent
motorina0 Mar 28, 2024
5293f3c
fix: user-agent again
motorina0 Mar 28, 2024
9852793
test: add `with error` test
motorina0 Mar 28, 2024
5ae01f2
feat: customize test name
motorina0 Mar 29, 2024
4deb564
fix: better exception handling for `status`
motorina0 Mar 29, 2024
e4a7061
fix: add `try-catch` for `raise_for_status`
motorina0 Mar 29, 2024
5f5519f
test: with no mocks
motorina0 Mar 29, 2024
0f7bdb6
chore: clean-up generalized tests
motorina0 Mar 29, 2024
695425c
chore: code format
motorina0 Mar 29, 2024
26e6370
chore: code format
motorina0 Mar 29, 2024
8d77ecb
chore: remove extracted tests
motorina0 Mar 29, 2024
f10cc40
test: add `create_invoice`: error test
motorina0 Mar 29, 2024
b0bedd5
add: test for `create_invoice` with http 404
motorina0 Mar 29, 2024
e995cda
test: extract `test_pay_invoice_ok`
motorina0 Mar 29, 2024
e38aee4
test: extract `test_pay_invoice_error_response`
motorina0 Mar 29, 2024
2ddfd35
test: extract `test_pay_invoice_http_404`
motorina0 Mar 29, 2024
a22fcd6
test: add "missing data"
motorina0 Mar 29, 2024
40ce5a4
test: add `bad-json`
motorina0 Mar 29, 2024
4e3c7aa
test: add `no mocks` for `create_invoice`
motorina0 Mar 29, 2024
0a7bbc2
test: add `no mocks` for `pay_invoice`
motorina0 Mar 29, 2024
8f9ff60
test: add `bad json` tests
motorina0 Apr 1, 2024
19bce42
chore: re-order tests
motorina0 Apr 1, 2024
fda9c0b
test: add `missing data` test for `pay_imvoice`
motorina0 Apr 1, 2024
d8d49bc
chore: re-order tests
motorina0 Apr 1, 2024
ad7ad47
test: add `success` test for `get_invoice_status `
motorina0 Apr 1, 2024
e65a840
feat: update test structure
motorina0 Apr 1, 2024
00d0a73
test: new status
motorina0 Apr 1, 2024
e56114d
test: add more test
motorina0 Apr 1, 2024
96e67ed
chore: code clean-up
motorina0 Apr 1, 2024
cd7fcf5
test: add success test for `get_payment_status `
motorina0 Apr 1, 2024
7091d48
test: add `pending` tests for `check_payment_status`
motorina0 Apr 1, 2024
2027712
chore: remove extracted tests
motorina0 Apr 1, 2024
c38f06f
test: add more tests
motorina0 Apr 1, 2024
c234434
test: add `no mocks` test
motorina0 Apr 1, 2024
092aa4f
fix: funding source loading
motorina0 Apr 1, 2024
ec2db50
refactor: start to extract data model
motorina0 Apr 2, 2024
417ac4f
chore: final clean-up
motorina0 Apr 2, 2024
ecc595c
chore: rename file
motorina0 Apr 2, 2024
2490d76
test: add tests for alby
motorina0 Apr 3, 2024
154c309
refactor: `KeyError` handling
motorina0 Apr 3, 2024
8b2b219
chore: log error
motorina0 Apr 3, 2024
0339bc0
chore: skip the negative fee test
motorina0 Apr 3, 2024
2f8c382
fix: error message fetching
motorina0 Apr 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 111 additions & 51 deletions lnbits/wallets/alby.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import asyncio
import hashlib
import json
from typing import AsyncGenerator, Dict, Optional

import httpx
Expand Down Expand Up @@ -42,17 +43,28 @@ async def cleanup(self):
async def status(self) -> StatusResponse:
try:
r = await self.client.get("/balance", timeout=10)
except (httpx.ConnectError, httpx.RequestError):
return StatusResponse(f"Unable to connect to '{self.endpoint}'", 0)
r.raise_for_status()

if r.is_error:
error_message = r.json()["message"]
return StatusResponse(error_message, 0)
data = r.json()

data = r.json()
assert data["unit"] == "sat"
# multiply balance by 1000 to get msats balance
return StatusResponse(None, data["balance"] * 1000)
if len(data) == 0:
return StatusResponse("no data", 0)

if r.is_error or data["unit"] != "sat":
error_message = data["message"] if "message" in data else r.text
return StatusResponse(f"Server error: '{error_message}'", 0)

# multiply balance by 1000 to get msats balance
return StatusResponse(None, data["balance"] * 1000)
except KeyError as exc:
logger.warning(exc)
return StatusResponse("Server error: 'missing required fields'", 0)
except json.JSONDecodeError as exc:
logger.warning(exc)
return StatusResponse("Server error: 'invalid json response'", 0)
except Exception as exc:
logger.warning(exc)
return StatusResponse(f"Unable to connect to {self.endpoint}.", 0)

async def create_invoice(
self,
Expand All @@ -71,57 +83,105 @@ async def create_invoice(
else:
data["memo"] = memo or ""

r = await self.client.post(
"/invoices",
json=data,
timeout=40,
)

if r.is_error:
error_message = r.json()["message"]
return InvoiceResponse(False, None, None, error_message)

data = r.json()
checking_id = data["payment_hash"]
payment_request = data["payment_request"]
return InvoiceResponse(True, checking_id, payment_request, None)
try:
r = await self.client.post(
"/invoices",
json=data,
timeout=40,
)
r.raise_for_status()

data = r.json()

if r.is_error:
error_message = data["message"] if "message" in data else r.text
return InvoiceResponse(False, None, None, error_message)

checking_id = data["payment_hash"]
payment_request = data["payment_request"]
return InvoiceResponse(True, checking_id, payment_request, None)
except KeyError as exc:
logger.warning(exc)
return InvoiceResponse(
False, None, None, "Server error: 'missing required fields'"
)
except json.JSONDecodeError as exc:
logger.warning(exc)
return InvoiceResponse(
False, None, None, "Server error: 'invalid json response'"
)
except Exception as exc:
logger.warning(exc)
return InvoiceResponse(
False, None, None, f"Unable to connect to {self.endpoint}."
)

async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
# https://api.getalby.com/payments/bolt11
r = await self.client.post(
"/payments/bolt11",
json={"invoice": bolt11}, # assume never need amount in body
timeout=None,
)

if r.is_error:
error_message = r.json()["message"]
return PaymentResponse(False, None, None, None, error_message)

data = r.json()
checking_id = data["payment_hash"]
fee_msat = -data["fee"]
preimage = data["payment_preimage"]

return PaymentResponse(True, checking_id, fee_msat, preimage, None)
try:
# https://api.getalby.com/payments/bolt11
r = await self.client.post(
"/payments/bolt11",
json={"invoice": bolt11}, # assume never need amount in body
timeout=None,
)
r.raise_for_status()
data = r.json()

if r.is_error:
error_message = data["message"] if "message" in data else r.text
return PaymentResponse(False, None, None, None, error_message)

checking_id = data["payment_hash"]
# todo: confirm with bitkarrot that having the minus is fine
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc: @bitkarrot ☝️

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the fee value returned by Alby is positive.

# other funding sources return a positive fee value
fee_msat = -data["fee"]
preimage = data["payment_preimage"]

return PaymentResponse(True, checking_id, fee_msat, preimage, None)
except KeyError as exc:
logger.warning(exc)
return PaymentResponse(
False, None, None, None, "Server error: 'missing required fields'"
)
except json.JSONDecodeError as exc:
logger.warning(exc)
return PaymentResponse(
False, None, None, None, "Server error: 'invalid json response'"
)
except Exception as exc:
logger.info(f"Failed to pay invoice {bolt11}")
logger.warning(exc)
return PaymentResponse(
False, None, None, None, f"Unable to connect to {self.endpoint}."
)

async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
return await self.get_payment_status(checking_id)

async def get_payment_status(self, checking_id: str) -> PaymentStatus:
r = await self.client.get(f"/invoices/{checking_id}")

if r.is_error:
try:
r = await self.client.get(f"/invoices/{checking_id}")

if r.is_error:
return PaymentPendingStatus()

data = r.json()

statuses = {
"CREATED": None,
"SETTLED": True,
}
# todo: extract fee and preimage
# maybe use the more specific endpoints:
# - https://api.getalby.com/invoices/incoming
# - https://api.getalby.com/invoices/outgoing
return PaymentStatus(
statuses[data.get("state")], fee_msat=None, preimage=None
)
except Exception as e:
logger.error(f"Error getting invoice status: {e}")
return PaymentPendingStatus()

data = r.json()

statuses = {
"CREATED": None,
"SETTLED": True,
}
return PaymentStatus(statuses[data.get("state")], fee_msat=None, preimage=None)

async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
self.queue: asyncio.Queue = asyncio.Queue(0)
while True:
Expand Down
2 changes: 0 additions & 2 deletions lnbits/wallets/lndrest.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,8 @@ async def status(self) -> StatusResponse:
r.raise_for_status()

data = r.json()

if len(data) == 0:
return StatusResponse("no data", 0)

if r.is_error or "balance" not in data:
return StatusResponse(f"Server error: '{r.text}'", 0)

Expand Down
9 changes: 9 additions & 0 deletions tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,13 @@ def rest_wallet_fixtures_from_json(path) -> List["WalletTest"]:
}
)
if "mocks" in test:
if fs_name not in test["mocks"]:
t.skip = True
tests[fs_name].append(t)
continue

test_mocks_names = test["mocks"][fs_name]

fs_mocks = fn["mocks"][fs_name]
for mock_name in fs_mocks:
for test_mock in test_mocks_names[mock_name]:
Expand All @@ -223,6 +229,7 @@ def rest_wallet_fixtures_from_json(path) -> List["WalletTest"]:
f"""{t.description}:{mock.description or ""}"""
)
unique_test.mocks = t.mocks + [mock]
unique_test.skip = mock.skip

tests[fs_name].append(unique_test)
else:
Expand All @@ -246,6 +253,7 @@ class FunctionMock(BaseModel):


class TestMock(BaseModel):
skip: Optional[bool]
description: Optional[str]
request_type: Optional[str]
request_body: Optional[dict]
Expand Down Expand Up @@ -279,6 +287,7 @@ class FunctionData(BaseModel):


class WalletTest(BaseModel):
skip: Optional[bool]
function: str
description: str
funding_source: FundingSourceConfig
Expand Down
Loading