blockstream-satellite-api/server/tests/common.py
Blockstream Satellite 5efcbd2418 Extend the states returned by /orders/:state
Previously, the /orders/:state endpoint only returned the "pending",
"queued", and "sent" states. This patch adds the following new states:
"paid", "transmitting", "confirming", "rx-pending", "retransmitting",
and "received". These are mapped directly to the order status, except
for the "rx-pending" and "retransmitting" states, which are inferred
based on other information. The "rx-pending" state returns orders with
sent status only, as opposed to the call to /orders/sent, which returns
orders with sent and received status. The "retransmitting" state returns
any order with entries on the retransmission database.
2023-02-10 14:40:32 -03:00

188 lines
6.2 KiB
Python

import datetime
import io
import json
import os
import random
import string
from http import HTTPStatus
from database import db
from models import Invoice, Order
from utils import hmac_sha256_digest
import bidding
import constants
def rnd_string(n_bytes):
"""Generate random string with given number of bytes"""
return ''.join(
random.choice(string.ascii_letters + string.digits)
for _ in range(n_bytes))
def upload_test_file(client, msg, bid, regions=[], channel=None, admin=False):
post_data = {'bid': bid, 'file': (io.BytesIO(msg.encode()), 'testfile')}
if len(regions) > 0:
post_data['regions'] = [regions]
if channel:
post_data['channel'] = channel
endpoint = '/admin/order' if admin else '/order'
return client.post(endpoint,
data=post_data,
content_type='multipart/form-data')
def place_order(client,
n_bytes,
regions=[],
bid=None,
channel=None,
admin=False):
if bid is None:
bid = bidding.get_min_bid(n_bytes)
msg = rnd_string(n_bytes)
return upload_test_file(client, msg, bid, regions, channel, admin)
def check_upload(order_uuid, expected_data):
path = os.path.join(constants.MSG_STORE_PATH, order_uuid)
assert os.path.exists(path)
with open(path) as fd:
upload_data = fd.read()
assert upload_data == expected_data
db_order = Order.query.filter_by(uuid=order_uuid).first()
assert db_order is not None
def check_invoice(generated_invoice, order_uuid):
db_order = Order.query.filter_by(uuid=order_uuid).first()
assert db_order is not None
db_invoice = \
Invoice.query.filter_by(lid=generated_invoice['id']).first()
assert db_invoice is not None
assert db_invoice.order_id == db_order.id
assert db_invoice.amount == db_order.unpaid_bid
def pay_invoice(invoice, client):
charged_auth_token = hmac_sha256_digest(constants.LIGHTNING_WEBHOOK_KEY,
invoice.lid)
rv = client.post(f'/callback/{invoice.lid}/{charged_auth_token}')
assert rv.status_code == HTTPStatus.OK
def confirm_tx(tx_seq_num, regions, client):
tx_rv = client.post(f'/order/tx/{tx_seq_num}', data={'regions': [regions]})
assert tx_rv.status_code == HTTPStatus.OK
def new_invoice(order_id, invoice_status, amount):
assert (isinstance(invoice_status, constants.InvoiceStatus))
lid = rnd_string(50)
return Invoice(
id=random.randint(1, 1000000),
lid=lid,
invoice=json.dumps({
"id":
lid,
"msatoshi":
amount,
"description":
"BSS Test",
"rhash":
"94855ac3b06543",
"payreq":
"lntb100n1psfy",
"expires_at":
str(datetime.datetime.utcnow() +
datetime.timedelta(seconds=constants.LN_INVOICE_EXPIRY)),
"created_at":
str(datetime.datetime.utcnow()),
"metadata": {
"uuid": "7f9a5b81-5358-4be0-9af6-b8c6fbac9dcd",
"sha256_message_digest":
"a591a6d40bf420404a011733cfb7b190d62c6"
},
"status":
"unpaid"
}),
order_id=order_id,
status=invoice_status.value,
amount=amount,
expires_at=datetime.datetime.utcnow() +
datetime.timedelta(seconds=constants.LN_INVOICE_EXPIRY))
def generate_test_order(mock_new_invoice,
client,
order_status=None,
invoice_status=constants.InvoiceStatus.pending,
tx_seq_num=None,
n_bytes=500,
bid=None,
order_id=1,
regions=[],
started_transmission_at=None,
channel=None,
admin=False):
"""Generate a valid order and add it to the database
This function generates an order with a related invoice with
given parameters and stores them in the database.
Args:
mock_new_invoice: A python mock for simulation
orders.new_invoice function
client: Flask client used to send api calls
order_status: status to be set for the generated order,
default input value is None but in the
database it will be set to pending
invoice_status: status to be set for the generated invoice,
default is pending
tx_seq_num: tx_seq_num value to be set for the generated
order, default value is None
n_bytes: length of generated message
bid: amount of bid, default value is None, if None a minimum
valid value will be set
order_id: the id to be used when connecting invoice to an
order, default value is 1
regions: list of regions over which this order should be
transmitted. The default value is an empty list implying
the order should be sent over all regions.
channel: Logical channel on which to transmit the order.
admin: Whether to post the order via the /admin/order route.
Returns:
The json response of the create order endpoint.
"""
assert (isinstance(invoice_status, constants.InvoiceStatus))
if not bid:
bid = bidding.get_min_bid(n_bytes)
mock_new_invoice.return_value = (True,
new_invoice(order_id, invoice_status,
bid))
post_rv = place_order(client, n_bytes, regions, bid, channel, admin)
assert post_rv.status_code == HTTPStatus.OK
uuid = post_rv.get_json()['uuid']
# Set order's sequence number and status
db_order = Order.query.filter_by(uuid=uuid).first()
if order_status:
assert (isinstance(order_status, constants.OrderStatus))
db_order.status = order_status.value
if tx_seq_num:
db_order.tx_seq_num = tx_seq_num
if started_transmission_at:
db_order.started_transmission_at = started_transmission_at
db.session.commit()
return post_rv.get_json()