Co-authored-by: alan <alan@lnbits.com> Co-authored-by: Tiago Vasconcelos <talvasconcelos@gmail.com>
296 lines
9.3 KiB
Python
296 lines
9.3 KiB
Python
import json
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
from pytest_mock.plugin import MockerFixture
|
|
|
|
from lnbits.core.db import core_app_extra
|
|
from lnbits.core.models.extensions import ExtensionPermission
|
|
from lnbits.core.wasm_ext.wasm.config import parse_wasm_extension_config
|
|
from lnbits.core.wasm_ext.wasm.events import (
|
|
_payment_extension_id,
|
|
_payment_source_id,
|
|
_wasm_public_invoice_source_tables_from_permissions,
|
|
dispatch_wasm_invoice_paid,
|
|
)
|
|
from lnbits.core.wasm_ext.wasm.loader import WasmExtension
|
|
from lnbits.helpers import sha256s
|
|
|
|
|
|
def test_wasm_invoice_paid_helpers_extract_extension_and_source_tables():
|
|
payment = SimpleNamespace(
|
|
extension="",
|
|
extra={"tag": "demoext", "source_id": "row-1"},
|
|
tag="fallback",
|
|
)
|
|
permissions = [
|
|
ExtensionPermission(
|
|
id="wallet.create_invoice_public",
|
|
policies=[
|
|
{"table": "tip_jars", "wallet_field": "wallet_id"},
|
|
{"table": "", "wallet_field": "wallet_id"},
|
|
],
|
|
),
|
|
ExtensionPermission(id="http.request"),
|
|
]
|
|
|
|
assert _payment_extension_id(payment) == "demoext"
|
|
assert _payment_source_id(payment) == "row-1"
|
|
assert _wasm_public_invoice_source_tables_from_permissions(permissions) == [
|
|
"tip_jars"
|
|
]
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_dispatch_wasm_invoice_paid_invokes_registered_event_export_with_owner(
|
|
mocker: MockerFixture,
|
|
):
|
|
ext_id = "demo_event_ext"
|
|
extension = _wasm_extension(ext_id)
|
|
registry = core_app_extra.wasm_extension_registry
|
|
registry.register(extension)
|
|
installed_extension = SimpleNamespace(
|
|
permissions=[
|
|
ExtensionPermission(
|
|
id="wallet.create_invoice_public",
|
|
policies=[{"table": "tip_jars", "wallet_field": "wallet_id"}],
|
|
)
|
|
]
|
|
)
|
|
mocker.patch(
|
|
"lnbits.core.wasm_ext.wasm.events.get_installed_extension",
|
|
mocker.AsyncMock(return_value=installed_extension),
|
|
)
|
|
mocker.patch(
|
|
"lnbits.core.wasm_ext.wasm.events.get_wallet",
|
|
mocker.AsyncMock(return_value=None),
|
|
)
|
|
storage_mock = mocker.patch(
|
|
"lnbits.core.wasm_ext.wasm.events.storage_get_row_owner_id",
|
|
mocker.AsyncMock(return_value="owner-1"),
|
|
)
|
|
invoke_mock = mocker.patch(
|
|
"lnbits.core.wasm_ext.wasm.events.invoke_wasm_extension_export",
|
|
mocker.AsyncMock(),
|
|
)
|
|
payment = _payment(ext_id)
|
|
|
|
try:
|
|
await dispatch_wasm_invoice_paid(payment)
|
|
finally:
|
|
registry._extensions.pop(ext_id, None)
|
|
|
|
storage_mock.assert_awaited_once_with(ext_id, "tip_jars", "row-1")
|
|
invoke_mock.assert_awaited_once()
|
|
assert invoke_mock.await_args is not None
|
|
args = invoke_mock.await_args.args
|
|
kwargs = invoke_mock.await_args.kwargs
|
|
assert args[0] == ext_id
|
|
assert args[1] == "on_invoice_paid"
|
|
assert args[2]["paymentHash"] == "payment-hash"
|
|
assert args[2]["payment"] == {"id": "payment-row"}
|
|
assert kwargs["context"] == "event"
|
|
assert kwargs["owner_id"] == "owner-1"
|
|
assert kwargs["trigger_type"] == "event"
|
|
assert kwargs["event_type"] == "invoice_paid"
|
|
assert kwargs["wallet_id"] == "wallet-1"
|
|
assert kwargs["payment_hash"] == "payment-hash"
|
|
assert kwargs["checking_id"] == "checking-id"
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_dispatch_wasm_invoice_paid_skips_invalid_event_export_visibility(
|
|
mocker: MockerFixture,
|
|
):
|
|
ext_id = "demo_public_event_ext"
|
|
extension = _wasm_extension(ext_id, visibility="public")
|
|
registry = core_app_extra.wasm_extension_registry
|
|
registry.register(extension)
|
|
invoke_mock = mocker.patch(
|
|
"lnbits.core.wasm_ext.wasm.events.invoke_wasm_extension_export",
|
|
mocker.AsyncMock(),
|
|
)
|
|
mocker.patch(
|
|
"lnbits.core.wasm_ext.wasm.events.get_wallet",
|
|
mocker.AsyncMock(return_value=None),
|
|
)
|
|
|
|
try:
|
|
await dispatch_wasm_invoice_paid(_payment(ext_id))
|
|
finally:
|
|
registry._extensions.pop(ext_id, None)
|
|
|
|
invoke_mock.assert_not_awaited()
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_dispatch_wasm_invoice_paid_invokes_wallet_watch_grant(
|
|
mocker: MockerFixture,
|
|
):
|
|
ext_id = "demo_wallet_watch_ext"
|
|
extension = _wasm_extension(ext_id)
|
|
registry = core_app_extra.wasm_extension_registry
|
|
registry.register(extension)
|
|
mocker.patch(
|
|
"lnbits.core.wasm_ext.wasm.events.get_wallet",
|
|
mocker.AsyncMock(return_value=SimpleNamespace(id="wallet-1", user="user-1")),
|
|
)
|
|
mocker.patch(
|
|
"lnbits.core.wasm_ext.wasm.events.get_user_extensions",
|
|
mocker.AsyncMock(
|
|
return_value=[
|
|
SimpleNamespace(
|
|
active=True,
|
|
extension=ext_id,
|
|
permissions={
|
|
"wallet.payments.watch": [
|
|
{
|
|
"id": "grant-1",
|
|
"wallet_id": "wallet-1",
|
|
"enabled": True,
|
|
}
|
|
]
|
|
},
|
|
)
|
|
]
|
|
),
|
|
)
|
|
mocker.patch(
|
|
"lnbits.core.wasm_ext.wasm.events.get_installed_extension",
|
|
mocker.AsyncMock(
|
|
return_value=SimpleNamespace(
|
|
active=True,
|
|
permissions=[ExtensionPermission(id="wallet.payments.watch")],
|
|
)
|
|
),
|
|
)
|
|
invoke_mock = mocker.patch(
|
|
"lnbits.core.wasm_ext.wasm.events.invoke_wasm_extension_export",
|
|
mocker.AsyncMock(),
|
|
)
|
|
|
|
try:
|
|
await dispatch_wasm_invoice_paid(_payment(""))
|
|
finally:
|
|
registry._extensions.pop(ext_id, None)
|
|
|
|
invoke_mock.assert_awaited_once()
|
|
assert invoke_mock.await_args is not None
|
|
args = invoke_mock.await_args.args
|
|
kwargs = invoke_mock.await_args.kwargs
|
|
assert args[0] == ext_id
|
|
assert args[1] == "on_invoice_paid"
|
|
assert args[2]["paymentHash"] == "payment-hash"
|
|
assert kwargs["context"] == "event"
|
|
assert kwargs["owner_id"] == sha256s("user-1")
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_dispatch_wasm_invoice_paid_dedupes_tagged_wallet_watch_grant(
|
|
mocker: MockerFixture,
|
|
):
|
|
ext_id = "demo_dedupe_event_ext"
|
|
extension = _wasm_extension(ext_id)
|
|
registry = core_app_extra.wasm_extension_registry
|
|
registry.register(extension)
|
|
mocker.patch(
|
|
"lnbits.core.wasm_ext.wasm.events.get_wallet",
|
|
mocker.AsyncMock(return_value=SimpleNamespace(id="wallet-1", user="user-1")),
|
|
)
|
|
mocker.patch(
|
|
"lnbits.core.wasm_ext.wasm.events.get_user_extensions",
|
|
mocker.AsyncMock(
|
|
return_value=[
|
|
SimpleNamespace(
|
|
active=True,
|
|
extension=ext_id,
|
|
permissions={
|
|
"wallet.payments.watch": [
|
|
{
|
|
"id": "grant-1",
|
|
"wallet_id": "wallet-1",
|
|
"enabled": True,
|
|
}
|
|
]
|
|
},
|
|
)
|
|
]
|
|
),
|
|
)
|
|
mocker.patch(
|
|
"lnbits.core.wasm_ext.wasm.events.get_installed_extension",
|
|
mocker.AsyncMock(
|
|
return_value=SimpleNamespace(
|
|
active=True,
|
|
permissions=[ExtensionPermission(id="wallet.payments.watch")],
|
|
)
|
|
),
|
|
)
|
|
invoke_mock = mocker.patch(
|
|
"lnbits.core.wasm_ext.wasm.events.invoke_wasm_extension_export",
|
|
mocker.AsyncMock(),
|
|
)
|
|
|
|
try:
|
|
await dispatch_wasm_invoice_paid(_payment(ext_id, extra={}))
|
|
finally:
|
|
registry._extensions.pop(ext_id, None)
|
|
|
|
invoke_mock.assert_awaited_once()
|
|
assert invoke_mock.await_args is not None
|
|
assert invoke_mock.await_args.kwargs["owner_id"] == sha256s("user-1")
|
|
|
|
|
|
def _wasm_extension(ext_id: str, *, visibility: str = "event") -> WasmExtension:
|
|
config = parse_wasm_extension_config(
|
|
ext_id,
|
|
{
|
|
"id": ext_id,
|
|
"name": "Demo event extension",
|
|
"short_description": "Demo",
|
|
"version": "1.0.0",
|
|
"extension_type": "wasm",
|
|
"wasm": {
|
|
"module": "extension.wasm",
|
|
"exports": [
|
|
{
|
|
"name": "on_invoice_paid",
|
|
"visibility": visibility,
|
|
}
|
|
],
|
|
},
|
|
"events": {"onInvoicePaid": "on_invoice_paid"},
|
|
},
|
|
)
|
|
root_path = Path(__file__).resolve().parent / ext_id
|
|
return WasmExtension(
|
|
id=ext_id,
|
|
name=config.name,
|
|
version=config.version,
|
|
root_path=root_path,
|
|
module_path=root_path / "extension.wasm",
|
|
wit_path=None,
|
|
world=config.wasm.world,
|
|
exports=config.wasm.exports,
|
|
config=config,
|
|
)
|
|
|
|
|
|
def _payment(ext_id: str, *, extra: dict | None = None) -> SimpleNamespace:
|
|
return SimpleNamespace(
|
|
extension=ext_id,
|
|
extra={"source_id": "row-1"} if extra is None else extra,
|
|
tag=None,
|
|
wallet_id="wallet-1",
|
|
payment_hash="payment-hash",
|
|
checking_id="checking-id",
|
|
amount=1000,
|
|
fee=0,
|
|
bolt11="lnbc1",
|
|
memo="memo",
|
|
pending=False,
|
|
status="success",
|
|
json=lambda: json.dumps({"id": "payment-row"}),
|
|
)
|