refactor: extract helpers, move private functions to the bottom

This commit is contained in:
Vlad Stan
2026-03-25 10:49:52 +02:00
parent 2c84f9781d
commit 0fd116e7ee
15 changed files with 366 additions and 422 deletions
+8 -32
View File
@@ -1,39 +1,11 @@
from io import BytesIO
from uuid import uuid4
import pytest
from fastapi import UploadFile
from httpx import AsyncClient
from PIL import Image
from starlette.datastructures import Headers
from lnbits.core.crud.assets import get_user_asset
from lnbits.core.services.assets import create_user_asset
def _png_bytes() -> bytes:
image = Image.new("RGB", (32, 32), color="blue")
buffer = BytesIO()
image.save(buffer, format="PNG")
return buffer.getvalue()
def _upload_file(contents: bytes, filename: str, content_type: str) -> UploadFile:
return UploadFile(
BytesIO(contents),
filename=filename,
headers=Headers({"content-type": content_type}),
)
async def _user_headers(client: AsyncClient, user_id: str) -> dict[str, str]:
response = await client.post("/api/v1/auth/usr", json={"usr": user_id})
client.cookies.clear()
access_token = response.json()["access_token"]
return {
"Authorization": f"Bearer {access_token}",
"Content-type": "application/json",
}
from tests.helpers import get_png_bytes, get_user_token_headers, make_upload_file
@pytest.mark.anyio
@@ -100,10 +72,14 @@ async def test_asset_api_enforces_visibility_and_supports_admin_updates(
):
private_asset = await create_user_asset(
from_user.id,
_upload_file(_png_bytes(), f"private_{uuid4().hex[:8]}.png", "image/png"),
make_upload_file(
get_png_bytes(),
filename=f"private_{uuid4().hex[:8]}.png",
content_type="image/png",
),
is_public=False,
)
other_user_headers = await _user_headers(client, to_user.id)
other_user_headers = await get_user_token_headers(client, to_user.id)
anonymous = await client.get(f"/api/v1/assets/{private_asset.id}/data")
assert anonymous.status_code == 404
@@ -152,7 +128,7 @@ async def test_asset_api_validates_uploads_and_missing_assets(
stored = await create_user_asset(
"missing-user-check",
_upload_file(b"content", "content.txt", "text/plain"),
make_upload_file(b"content", filename="content.txt", content_type="text/plain"),
is_public=True,
)
fetched = await get_user_asset("missing-user-check", stored.id)
+9 -48
View File
@@ -19,7 +19,6 @@ from lnbits.core.models.extensions import (
CreateExtensionReview,
Extension,
ExtensionConfig,
ExtensionMeta,
ExtensionRelease,
InstallableExtension,
PayToEnableInfo,
@@ -49,45 +48,7 @@ from lnbits.core.views.extension_api import (
get_pay_to_enable_invoice,
get_pay_to_install_invoice,
)
def _release(ext_id: str, version: str = "1.0.0") -> ExtensionRelease:
return ExtensionRelease(
name=ext_id,
version=version,
archive=f"https://example.com/{ext_id}.zip",
source_repo="org/repo",
hash=f"hash-{ext_id}",
details_link=f"https://example.com/{ext_id}/details.json",
repo=f"https://github.com/org/{ext_id}",
icon=f"/{ext_id}/static/icon.png",
pay_link=f"https://pay.example/{ext_id}",
)
def _installable_extension(
ext_id: str,
*,
active: bool = True,
pay_to_enable: PayToEnableInfo | None = None,
dependencies: list[str] | None = None,
payments: list[ReleasePaymentInfo] | None = None,
) -> InstallableExtension:
release = _release(ext_id)
return InstallableExtension(
id=ext_id,
name=f"Extension {ext_id}",
version=release.version,
active=active,
short_description="Demo extension",
icon=release.icon,
meta=ExtensionMeta(
installed_release=release,
pay_to_enable=pay_to_enable,
dependencies=dependencies or [],
payments=payments or [],
),
)
from tests.helpers import make_extension_release, make_installable_extension
class _MockHTTPResponse:
@@ -132,7 +93,7 @@ class _MockHTTPClient:
@pytest.mark.anyio
async def test_extension_api_install_details_and_release_endpoints(mocker):
ext_id = f"ext_{uuid4().hex[:8]}"
release = _release(ext_id)
release = make_extension_release(ext_id)
create_data = CreateExtension(
ext_id=ext_id,
archive=release.archive,
@@ -172,7 +133,7 @@ async def test_extension_api_install_details_and_release_endpoints(mocker):
assert details["icon"] == release.icon
assert details["repo"] == release.repo
installed_ext = _installable_extension(
installed_ext = make_installable_extension(
ext_id,
payments=[
ReleasePaymentInfo(
@@ -218,7 +179,7 @@ async def test_extension_api_pay_to_enable_and_catalog_views(mocker, admin_user)
ext_id = f"paid_{uuid4().hex[:8]}"
await create_installed_extension(
_installable_extension(
make_installable_extension(
ext_id,
pay_to_enable=PayToEnableInfo(
required=True, amount=10, wallet=admin_wallet.id
@@ -294,7 +255,7 @@ async def test_extension_api_pay_to_enable_and_catalog_views(mocker, admin_user)
visible_extensions = await api_get_user_extensions(AccountId(id=regular_user.id))
assert [ext.code for ext in visible_extensions] == [ext_id]
catalog_entry = _installable_extension(
catalog_entry = make_installable_extension(
ext_id,
pay_to_enable=PayToEnableInfo(required=True, amount=21, wallet=admin_wallet.id),
)
@@ -315,11 +276,11 @@ async def test_extension_api_activate_uninstall_install_invoice_and_cleanup(mock
uninstall_ext = f"uninstall_{uuid4().hex[:8]}"
db_ext = f"db_{uuid4().hex[:8]}"
await create_installed_extension(_installable_extension(base_ext))
await create_installed_extension(make_installable_extension(base_ext))
await create_installed_extension(
_installable_extension(dependent_ext, dependencies=[base_ext])
make_installable_extension(dependent_ext, dependencies=[base_ext])
)
await create_installed_extension(_installable_extension(uninstall_ext))
await create_installed_extension(make_installable_extension(uninstall_ext))
mocker.patch(
"lnbits.core.views.extension_api.get_valid_extensions",
@@ -370,7 +331,7 @@ async def test_extension_api_activate_uninstall_install_invoice_and_cleanup(mock
install_invoice = await create_wallet_invoice(
wallet.id, CreateInvoice(out=False, amount=33, memo="install extension")
)
release = _release(base_ext, version="2.0.0")
release = make_extension_release(base_ext, version="2.0.0")
payment_info = ReleasePaymentInfo(
amount=33,
pay_link=release.pay_link,
+9 -50
View File
@@ -7,19 +7,8 @@ from lnbits.core.crud.extensions import create_user_extension, get_user_extensio
from lnbits.core.crud.users import get_account
from lnbits.core.models.extensions import (
Extension,
ExtensionRelease,
UserExtension,
)
from lnbits.core.models.extensions_builder import (
ActionFields,
ClientDataFields,
DataField,
DataFields,
ExtensionData,
OwnerDataFields,
PublicPageFields,
SettingsFields,
)
from lnbits.core.models.users import AccountId
from lnbits.core.views.extensions_builder_api import (
api_build_extension,
@@ -28,41 +17,7 @@ from lnbits.core.views.extensions_builder_api import (
api_preview_extension,
)
from lnbits.settings import Settings
def _extension_data(ext_id: str = "demoext") -> ExtensionData:
return ExtensionData(
id=ext_id,
name="Demo Extension",
stub_version="0.1.0",
short_description="Generated extension",
owner_data=DataFields(
name="OwnerData",
fields=[DataField(name="wallet_id", type="wallet")],
),
client_data=DataFields(
name="ClientData",
fields=[DataField(name="amount", type="int")],
),
settings_data=SettingsFields(name="SettingsData", fields=[]),
public_page=PublicPageFields(
owner_data_fields=OwnerDataFields(),
client_data_fields=ClientDataFields(),
action_fields=ActionFields(),
),
)
def _release(ext_id: str) -> ExtensionRelease:
return ExtensionRelease(
name=ext_id,
version="0.1.0",
archive=f"https://example.com/{ext_id}.zip",
source_repo="org/repo",
is_github_release=False,
hash=f"hash-{ext_id}",
icon=f"/{ext_id}/static/image/{ext_id}.png",
)
from tests.helpers import make_extension_data, make_extension_release
@pytest.mark.anyio
@@ -70,7 +25,7 @@ async def test_extensions_builder_api_build_preview_and_cleanup(
tmp_path, settings: Settings, mocker, from_user
):
ext_id = f"builder_{uuid4().hex[:8]}"
data = _extension_data(ext_id)
data = make_extension_data(ext_id)
build_dir = tmp_path / "build"
build_dir.mkdir(parents=True, exist_ok=True)
(build_dir / "index.txt").write_text("hello")
@@ -78,7 +33,9 @@ async def test_extensions_builder_api_build_preview_and_cleanup(
original_data_folder = settings.lnbits_data_folder
build_mock = mocker.patch(
"lnbits.core.views.extensions_builder_api.build_extension_from_data",
mocker.AsyncMock(return_value=(_release(ext_id), build_dir)),
mocker.AsyncMock(
return_value=(make_extension_release(ext_id, "0.1.0"), build_dir)
),
)
clean_mock = mocker.patch(
"lnbits.core.views.extensions_builder_api.clean_extension_builder_data"
@@ -108,7 +65,7 @@ async def test_extensions_builder_api_deploy_updates_user_extension(
tmp_path, settings: Settings, mocker, admin_user
):
ext_id = f"deploy_{uuid4().hex[:8]}"
data = _extension_data(ext_id)
data = make_extension_data(ext_id)
account = await get_account(admin_user.id)
assert account is not None
@@ -123,7 +80,9 @@ async def test_extensions_builder_api_deploy_updates_user_extension(
mocker.patch(
"lnbits.core.views.extensions_builder_api.build_extension_from_data",
mocker.AsyncMock(return_value=(_release(ext_id), build_root)),
mocker.AsyncMock(
return_value=(make_extension_release(ext_id, "0.1.0"), build_root)
),
)
install_mock = mocker.patch(
"lnbits.core.views.extensions_builder_api.install_extension",
+4 -15
View File
@@ -1,7 +1,6 @@
from uuid import uuid4
import pytest
from bolt11.types import MilliSatoshi
from fastapi import HTTPException
from lnurl import (
LnAddress,
@@ -13,7 +12,7 @@ from lnurl import (
LnurlResponseException,
)
from lnurl.models import MessageAction
from lnurl.types import CallbackUrl, LightningInvoice, LnurlPayMetadata
from lnurl.types import CallbackUrl, LightningInvoice
from pydantic import parse_obj_as
from lnbits.core.models import Account, CreateInvoice
@@ -27,6 +26,7 @@ from lnbits.core.views.lnurl_api import (
api_payments_pay_lnurl,
api_perform_lnurlauth,
)
from tests.helpers import make_lnurl_pay_response
TEST_BOLT11 = (
"lnbc1pnsu5z3pp57getmdaxhg5kc9yh2a2qsh7cjf4gnccgkw0qenm8vsqv50w7s"
@@ -37,20 +37,9 @@ TEST_BOLT11 = (
)
def _pay_response() -> LnurlPayResponse:
return LnurlPayResponse(
callback=parse_obj_as(CallbackUrl, "https://example.com/callback"),
minSendable=MilliSatoshi(1_000),
maxSendable=MilliSatoshi(10_000),
metadata=LnurlPayMetadata(
'[["text/plain","Test payment"],["text/identifier","alice@example.com"]]'
),
)
@pytest.mark.anyio
async def test_lnurl_api_scan_routes_validate_and_forward(mocker):
pay_response = _pay_response()
pay_response = make_lnurl_pay_response()
mocker.patch(
"lnbits.core.views.lnurl_api.lnurl_handle",
mocker.AsyncMock(return_value=pay_response),
@@ -92,7 +81,7 @@ async def test_lnurl_api_auth_and_pay_flow(mocker):
)
wallet = user.wallets[0]
wallet_info = WalletTypeInfo(key_type=KeyType.admin, wallet=wallet)
pay_response = _pay_response()
pay_response = make_lnurl_pay_response()
payment = await create_wallet_invoice(
wallet.id, CreateInvoice(out=False, amount=21, memo="lnurl")
)
+24 -24
View File
@@ -33,30 +33,6 @@ ZERO_AMOUNT_INVOICE = (
)
async def _create_payment(
wallet_id: str,
*,
amount_msat: int,
status: PaymentState = PaymentState.SUCCESS,
payment_hash: str | None = None,
tag: str | None = None,
) -> str:
checking_id = f"checking_{uuid4().hex[:8]}"
await create_payment(
checking_id=checking_id,
data=CreatePayment(
wallet_id=wallet_id,
payment_hash=payment_hash or uuid4().hex,
bolt11=f"bolt11_{checking_id}",
amount_msat=amount_msat,
memo=f"payment_{checking_id}",
extra={"tag": tag} if tag else {},
),
status=status,
)
return checking_id
@pytest.mark.anyio
async def test_payment_api_stats_and_all_paginated(admin_user):
first_user = await create_user_account(
@@ -185,3 +161,27 @@ async def test_payment_api_fee_reserve_and_hold_invoice_actions(mocker):
)
assert cancelled.failed is True
cancel_mock.assert_awaited_once()
async def _create_payment(
wallet_id: str,
*,
amount_msat: int,
status: PaymentState = PaymentState.SUCCESS,
payment_hash: str | None = None,
tag: str | None = None,
) -> str:
checking_id = f"checking_{uuid4().hex[:8]}"
await create_payment(
checking_id=checking_id,
data=CreatePayment(
wallet_id=wallet_id,
payment_hash=payment_hash or uuid4().hex,
bolt11=f"bolt11_{checking_id}",
amount_msat=amount_msat,
memo=f"payment_{checking_id}",
extra={"tag": tag} if tag else {},
),
status=status,
)
return checking_id
+4 -4
View File
@@ -8,10 +8,6 @@ from lnbits.core.models.users import Account
from lnbits.core.services.users import create_user_account
def _admin_headers(adminkey: str) -> dict[str, str]:
return {"X-Api-Key": adminkey, "Content-type": "application/json"}
@pytest.mark.anyio
async def test_wallet_api_share_invite_reject_accept_and_delete(
http_client: AsyncClient,
@@ -188,3 +184,7 @@ async def test_wallet_api_shared_wallet_requires_source_id(http_client: AsyncCli
assert (
response.json()["detail"] == "Shared wallet ID is required for shared wallets."
)
def _admin_headers(adminkey: str) -> dict[str, str]:
return {"X-Api-Key": adminkey, "Content-type": "application/json"}