mirror of
https://github.com/Blockstream/satellite-api.git
synced 2025-03-12 10:30:09 +01:00
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.
188 lines
6.2 KiB
Python
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()
|