Testing/click (#99)

* annotate context

* remove whitespace

* test CLI

* make format

* github action with submodule checkout

* maybe now

* vllt ja nu

* und no?

* back to normal mint running

* githuuuuub

* COME OOOON!

* SO. CLOSE.

* make format

* new test

* fix it

* make format

* receive v1 token test
This commit is contained in:
calle
2023-01-19 14:13:54 +01:00
committed by GitHub
parent aa20572150
commit 9acac156a7
8 changed files with 200 additions and 30 deletions

View File

@@ -11,7 +11,10 @@ jobs:
python-version: ["3.9"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v2
- name: Checkout repository and submodules
uses: actions/checkout@v2
with:
submodules: recursive
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
@@ -29,14 +32,15 @@ jobs:
LIGHTNING: False
MINT_PRIVATE_KEY: "testingkey"
MINT_SERVER_HOST: 0.0.0.0
MINT_SERVER_PORT: 3338
MINT_SERVER_PORT: 3337
run: |
nohup poetry run mint &
- name: Run tests
env:
LIGHTNING: False
WALLET_NAME: test_wallet
MINT_HOST: localhost
MINT_PORT: 3338
MINT_PORT: 3337
TOR: False
run: |
poetry run pytest tests --cov-report xml --cov cashu

View File

@@ -1,5 +1,8 @@
from typing import Optional
import click
import uvicorn
from click import Context
from cashu.core.settings import MINT_SERVER_HOST, MINT_SERVER_PORT
@@ -16,11 +19,11 @@ from cashu.core.settings import MINT_SERVER_HOST, MINT_SERVER_PORT
@click.option("--ssl-certfile", default=None, help="Path to SSL certificate")
@click.pass_context
def main(
ctx,
ctx: Context,
port: int = MINT_SERVER_PORT,
host: str = MINT_SERVER_HOST,
ssl_keyfile: str = None,
ssl_certfile: str = None,
ssl_keyfile: Optional[str] = None,
ssl_certfile: Optional[str] = None,
):
"""This routine starts the uvicorn server if the Cashu mint is
launched with `poetry run mint` at root level"""

View File

@@ -16,6 +16,7 @@ from os.path import isdir, join
from typing import Dict, List
import click
from click import Context
from loguru import logger
from cashu.core.base import Proof, TokenV2
@@ -80,7 +81,7 @@ class NaturalOrderGroup(click.Group):
help="Wallet name (default: wallet).",
)
@click.pass_context
def cli(ctx, host: str, walletname: str):
def cli(ctx: Context, host: str, walletname: str):
if TOR and not TorProxy().check_platform():
error_str = "Your settings say TOR=true but the built-in Tor bundle is not supported on your system. You have two options: Either install Tor manually and set TOR=FALSE and SOCKS_HOST=localhost and SOCKS_PORT=9050 in your Cashu config (recommended). Or turn off Tor by setting TOR=false (not recommended). Cashu will not work until you edit your config file accordingly."
error_str += "\n\n"
@@ -124,7 +125,7 @@ def coro(f):
)
@click.pass_context
@coro
async def pay(ctx, invoice: str, yes: bool):
async def pay(ctx: Context, invoice: str, yes: bool):
wallet: Wallet = ctx.obj["WALLET"]
await wallet.load_mint()
wallet.status()
@@ -151,7 +152,7 @@ async def pay(ctx, invoice: str, yes: bool):
@click.option("--hash", default="", help="Hash of the paid invoice.", type=str)
@click.pass_context
@coro
async def invoice(ctx, amount: int, hash: str):
async def invoice(ctx: Context, amount: int, hash: str):
wallet: Wallet = ctx.obj["WALLET"]
await wallet.load_mint()
wallet.status()
@@ -203,7 +204,7 @@ async def invoice(ctx, amount: int, hash: str):
)
@click.pass_context
@coro
async def balance(ctx, verbose):
async def balance(ctx: Context, verbose):
wallet: Wallet = ctx.obj["WALLET"]
if verbose:
# show balances per keyset
@@ -227,7 +228,7 @@ async def balance(ctx, verbose):
print(f"Balance: {wallet.available_balance} sat")
async def nostr_send(ctx, amount: int, pubkey: str, verbose: bool, yes: bool):
async def nostr_send(ctx: Context, amount: int, pubkey: str, verbose: bool, yes: bool):
"""
Sends tokens via nostr.
"""
@@ -259,7 +260,7 @@ async def nostr_send(ctx, amount: int, pubkey: str, verbose: bool, yes: bool):
client.close()
async def send(ctx, amount: int, lock: str, legacy: bool):
async def send(ctx: Context, amount: int, lock: str, legacy: bool):
"""
Prints token to send to stdout.
"""
@@ -342,7 +343,7 @@ async def send_command(
await nostr_send(ctx, amount, nostr, verbose, yes)
async def receive(ctx, token: str, lock: str):
async def receive(ctx: Context, token: str, lock: str):
wallet: Wallet = ctx.obj["WALLET"]
await wallet.load_mint()
@@ -419,7 +420,7 @@ async def receive(ctx, token: str, lock: str):
wallet.status()
async def receive_nostr(ctx, verbose: bool):
async def receive_nostr(ctx: Context, verbose: bool):
if NOSTR_PRIVATE_KEY is None:
print(
"Warning: No nostr private key set! You don't have NOSTR_PRIVATE_KEY set in your .env file. I will create a random private key for this session but I will not remember it."
@@ -469,7 +470,7 @@ async def receive_nostr(ctx, verbose: bool):
)
@click.pass_context
@coro
async def receive_cli(ctx, token: str, lock: str, nostr: bool, verbose: bool):
async def receive_cli(ctx: Context, token: str, lock: str, nostr: bool, verbose: bool):
wallet: Wallet = ctx.obj["WALLET"]
wallet.status()
if token:
@@ -488,7 +489,7 @@ async def receive_cli(ctx, token: str, lock: str, nostr: bool, verbose: bool):
)
@click.pass_context
@coro
async def burn(ctx, token: str, all: bool, force: bool):
async def burn(ctx: Context, token: str, all: bool, force: bool):
wallet: Wallet = ctx.obj["WALLET"]
await wallet.load_mint()
if not (all or token or force) or (token and all):
@@ -521,7 +522,7 @@ async def burn(ctx, token: str, all: bool, force: bool):
)
@click.pass_context
@coro
async def pending(ctx, legacy):
async def pending(ctx: Context, legacy):
wallet: Wallet = ctx.obj["WALLET"]
reserved_proofs = await get_reserved_proofs(wallet.db)
if len(reserved_proofs):
@@ -657,7 +658,7 @@ async def wallets(ctx):
@cli.command("info", help="Information about Cashu wallet.")
@click.pass_context
@coro
async def info(ctx):
async def info(ctx: Context):
print(f"Version: {VERSION}")
print(f"Wallet: {ctx.obj['WALLET_NAME']}")
if DEBUG:

View File

@@ -3,6 +3,7 @@ import urllib.parse
from typing import List
import click
from click import Context
from loguru import logger
from cashu.core.base import Proof, TokenV2, TokenV2Mint, WalletKeyset
@@ -11,7 +12,7 @@ from cashu.wallet.crud import get_keyset
from cashu.wallet.wallet import Wallet as Wallet
async def verify_mints(ctx, token: TokenV2):
async def verify_mints(ctx: Context, token: TokenV2):
"""
A helper function that iterates through all mints in the token and if it has
not been encountered before, asks the user to confirm.
@@ -60,7 +61,7 @@ async def verify_mints(ctx, token: TokenV2):
assert trust_token_mints, Exception("Aborted!")
async def redeem_multimint(ctx, token: TokenV2, script, signature):
async def redeem_multimint(ctx: Context, token: TokenV2, script, signature):
"""
Helper function to iterate thruogh a token with multiple mints and redeem them from
these mints one keyset at a time.
@@ -88,7 +89,7 @@ async def redeem_multimint(ctx, token: TokenV2, script, signature):
)
async def print_mint_balances(ctx, wallet, show_mints=False):
async def print_mint_balances(ctx: Context, wallet, show_mints=False):
"""
Helper function that prints the balances for each mint URL that we have tokens from.
"""
@@ -114,7 +115,7 @@ async def print_mint_balances(ctx, wallet, show_mints=False):
print("")
async def get_mint_wallet(ctx):
async def get_mint_wallet(ctx: Context):
"""
Helper function that asks the user for an input to select which mint they want to load.
Useful for selecting the mint that the user wants to send tokens from.

View File

@@ -140,7 +140,6 @@ This token format includes information about the mint as well. The field `proofs
}
]
}
```
When serialized, this becomes:

42
tests/conftest.py Normal file
View File

@@ -0,0 +1,42 @@
import multiprocessing
import time
import pytest
import pytest_asyncio
import uvicorn
from uvicorn import Config, Server
from cashu.core.migrations import migrate_databases
from cashu.wallet import migrations
from cashu.wallet.wallet import Wallet
SERVER_ENDPOINT = "http://localhost:3337"
class UvicornServer(multiprocessing.Process):
def __init__(self, config: Config):
super().__init__()
self.server = Server(config=config)
self.config = config
def stop(self):
self.terminate()
def run(self, *args, **kwargs):
self.server.run()
@pytest.fixture(autouse=True, scope="session")
def mint():
config = uvicorn.Config(
"cashu.mint.app:app",
port=3337,
host="127.0.0.1",
)
server = UvicornServer(config=config)
server.start()
time.sleep(1)
yield server
server.stop()

117
tests/test_cli.py Normal file
View File

@@ -0,0 +1,117 @@
import asyncio
import click
import pytest
from click.testing import CliRunner
from cashu.core.migrations import migrate_databases
from cashu.core.settings import VERSION
from cashu.wallet import migrations
from cashu.wallet.cli import cli
from cashu.wallet.wallet import Wallet
from tests.conftest import SERVER_ENDPOINT, mint
cli_prefix = ["--wallet", "test_wallet", "--host", SERVER_ENDPOINT]
async def init_wallet():
wallet = Wallet(SERVER_ENDPOINT, "data/test_wallet", "wallet")
await migrate_databases(wallet.db, migrations)
await wallet.load_proofs()
return wallet
@pytest.mark.asyncio
def test_info():
runner = CliRunner()
result = runner.invoke(
cli,
[*cli_prefix, "info"],
)
print("INFO")
print(result.output)
result.output.startswith(f"Version: {VERSION}")
assert result.exit_code == 0
@pytest.mark.asyncio
def test_balance():
runner = CliRunner()
result = runner.invoke(
cli,
[*cli_prefix, "balance"],
)
print("------ BALANCE ------")
print(result.output)
wallet = asyncio.run(init_wallet())
assert f"Balance: {wallet.available_balance} sat" in result.output
assert result.exit_code == 0
@pytest.mark.asyncio
def test_wallets():
runner = CliRunner()
result = runner.invoke(
cli,
[*cli_prefix, "wallets"],
)
print("WALLETS")
# on github this is empty
if len(result.output):
assert "test_wallet" in result.output
assert result.exit_code == 0
@pytest.mark.asyncio
def test_invoice():
runner = CliRunner()
result = runner.invoke(
cli,
[*cli_prefix, "invoice", "1000"],
)
print("INVOICE")
print(result.output)
wallet = asyncio.run(init_wallet())
assert f"Balance: {wallet.available_balance} sat" in result.output
assert result.exit_code == 0
@pytest.mark.asyncio
def test_send(mint):
runner = CliRunner()
result = runner.invoke(
cli,
[*cli_prefix, "send", "10"],
)
print("SEND")
print(result.output)
token = [l for l in result.output.split("\n") if l.startswith("ey")][0]
print("TOKEN")
print(token)
@pytest.mark.asyncio
def test_receive_tokenv2(mint):
runner = CliRunner()
token = "eyJwcm9vZnMiOiBbeyJpZCI6ICJEU0FsOW52dnlmdmEiLCAiYW1vdW50IjogMiwgInNlY3JldCI6ICJ3MEs4dE9OcFJOdVFvUzQ1Y2g1NkJ3IiwgIkMiOiAiMDI3NzcxODY4NWQ0MDgxNmQ0MTdmZGE1NWUzN2YxOTFkN2E5ODA0N2QyYWE2YzFlNDRhMWZjNTM1ZmViZDdjZDQ5In0sIHsiaWQiOiAiRFNBbDludnZ5ZnZhIiwgImFtb3VudCI6IDgsICJzZWNyZXQiOiAiX2J4cDVHeG1JQUVaRFB5Sm5qaFUxdyIsICJDIjogIjAzZTY2M2UzOWYyNTZlZTAzOTBiNGFiMThkZDA2OTc0NjRjZjIzYTM4OTc1MDlmZDFlYzQ1MzMxMTRlMTcwMDQ2NCJ9XSwgIm1pbnRzIjogW3sidXJsIjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzNyIsICJpZHMiOiBbIkRTQWw5bnZ2eWZ2YSJdfV19"
result = runner.invoke(
cli,
[*cli_prefix, "receive", token],
)
print("RECEIVE")
print(result.output)
@pytest.mark.asyncio
def test_receive_tokenv1(mint):
runner = CliRunner()
token = "3siaWQiOiAiRFNBbDludnZ5ZnZhIiwgImFtb3VudCI6IDIsICJzZWNyZXQiOiAiX3VOV1ZNeDRhQndieWszRDZoLWREZyIsICJDIjogIjAyMmEzMzRmZTIzYTA1OTJhZmM3OTk3OWQyZDJmMmUwOTgxMGNkZTRlNDY5ZGYwYzZhMGE4ZDg0ZmY1MmIxOTZhNyJ9LCB7ImlkIjogIkRTQWw5bnZ2eWZ2YSIsICJhbW91bnQiOiA4LCAic2VjcmV0IjogIk9VUUxnRE90WXhHOXJUMzZKdHFwbWciLCAiQyI6ICIwMzVmMGM2NTNhNTEzMGY4ZmQwNjY5NDg5YzEwMDY3N2Q5NGU0MGFlZjhkYWE0OWZiZDIyZTgzZjhjNThkZjczMTUifV0"
result = runner.invoke(
cli,
[*cli_prefix, "receive", token],
)
print("RECEIVE")
print(result.output)

View File

@@ -12,8 +12,7 @@ from cashu.wallet import migrations
from cashu.wallet.wallet import Wallet
from cashu.wallet.wallet import Wallet as Wallet1
from cashu.wallet.wallet import Wallet as Wallet2
SERVER_ENDPOINT = "http://localhost:3338"
from tests.conftest import SERVER_ENDPOINT, mint
async def assert_err(f, msg):
@@ -32,7 +31,7 @@ def assert_amt(proofs: List[Proof], expected: int):
@pytest_asyncio.fixture(scope="function")
async def wallet1():
async def wallet1(mint):
wallet1 = Wallet1(SERVER_ENDPOINT, "data/wallet1", "wallet1")
await migrate_databases(wallet1.db, migrations)
await wallet1.load_mint()
@@ -41,7 +40,7 @@ async def wallet1():
@pytest_asyncio.fixture(scope="function")
async def wallet2():
async def wallet2(mint):
wallet2 = Wallet2(SERVER_ENDPOINT, "data/wallet2", "wallet2")
await migrate_databases(wallet2.db, migrations)
await wallet2.load_mint()
@@ -53,6 +52,7 @@ async def wallet2():
async def test_get_keys(wallet1: Wallet):
assert len(wallet1.keys) == MAX_ORDER
keyset = await wallet1._get_keys(wallet1.url)
assert keyset.id is not None
assert type(keyset.id) == str
assert len(keyset.id) > 0
@@ -63,7 +63,10 @@ async def test_get_keyset(wallet1: Wallet):
# ket's get the keys first so we can get a keyset ID that we use later
keys1 = await wallet1._get_keys(wallet1.url)
# gets the keys of a specific keyset
assert keys1.id is not None
assert keys1.public_keys is not None
keys2 = await wallet1._get_keyset(wallet1.url, keys1.id)
assert keys2.public_keys is not None
assert len(keys1.public_keys) == len(keys2.public_keys)
@@ -156,7 +159,7 @@ async def test_duplicate_proofs_double_spent(wallet1: Wallet):
@pytest.mark.asyncio
async def test_send_and_redeem(wallet1: Wallet, wallet2: Wallet):
await wallet1.mint(64)
_, spendable_proofs = await wallet1.split_to_send(
_, spendable_proofs = await wallet1.split_to_send( # type: ignore
wallet1.proofs, 32, set_reserved=True
)
await wallet2.redeem(spendable_proofs)
@@ -229,7 +232,7 @@ async def test_p2sh_receive_wrong_script(wallet1: Wallet, wallet2: Wallet):
p2shscript = await wallet1.create_p2sh_lock()
txin_p2sh_address = p2shscript.address
lock = f"P2SH:{txin_p2sh_address}"
_, send_proofs = await wallet1.split_to_send(wallet1.proofs, 8, lock)
_, send_proofs = await wallet1.split_to_send(wallet1.proofs, 8, lock) # type: ignore
wrong_script = "asad" + p2shscript.script
@@ -249,7 +252,7 @@ async def test_p2sh_receive_wrong_signature(wallet1: Wallet, wallet2: Wallet):
p2shscript = await wallet1.create_p2sh_lock()
txin_p2sh_address = p2shscript.address
lock = f"P2SH:{txin_p2sh_address}"
_, send_proofs = await wallet1.split_to_send(wallet1.proofs, 8, lock)
_, send_proofs = await wallet1.split_to_send(wallet1.proofs, 8, lock) # type: ignore
wrong_signature = "asda" + p2shscript.signature