2021-07-20 11:00:49 -03:00
|
|
|
import io
|
|
|
|
import os
|
|
|
|
import pytest
|
|
|
|
from http import HTTPStatus
|
|
|
|
from unittest.mock import patch
|
|
|
|
from uuid import uuid4
|
|
|
|
|
2021-12-21 16:32:10 -03:00
|
|
|
from constants import InvoiceStatus, OrderStatus
|
2021-07-20 11:00:49 -03:00
|
|
|
from database import db
|
|
|
|
from error import assert_error, get_http_error_resp
|
2023-02-16 15:48:29 -03:00
|
|
|
from models import Invoice, Order, RxConfirmation
|
2021-07-20 11:00:49 -03:00
|
|
|
from order_helpers import adjust_bids, _paid_invoices_total,\
|
|
|
|
_unpaid_invoices_total
|
2021-12-21 16:32:10 -03:00
|
|
|
from regions import Regions, SATELLITE_REGIONS
|
2021-07-20 11:00:49 -03:00
|
|
|
from utils import hmac_sha256_digest
|
|
|
|
import bidding
|
|
|
|
import constants
|
|
|
|
import server
|
|
|
|
|
2021-12-21 16:32:30 -03:00
|
|
|
from common import check_invoice, pay_invoice, check_upload, new_invoice,\
|
2021-07-20 11:00:49 -03:00
|
|
|
place_order, generate_test_order, rnd_string, upload_test_file
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
2021-12-21 16:31:41 -03:00
|
|
|
def client(mockredis):
|
2021-07-20 11:00:49 -03:00
|
|
|
app = server.create_app(from_test=True)
|
|
|
|
app.app_context().push()
|
|
|
|
with app.test_client() as client:
|
|
|
|
yield client
|
|
|
|
server.teardown_app(app)
|
|
|
|
|
|
|
|
|
|
|
|
def check_received_message(order_uuid, received_message):
|
|
|
|
path = os.path.join(constants.MSG_STORE_PATH, order_uuid)
|
|
|
|
assert os.path.exists(path)
|
|
|
|
with open(path, 'rb') as fd:
|
|
|
|
sent_message = fd.read()
|
|
|
|
assert sent_message == received_message
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_file_upload(mock_new_invoice, client):
|
|
|
|
n_bytes = 500
|
|
|
|
bid = bidding.get_min_bid(n_bytes)
|
|
|
|
msg = rnd_string(n_bytes)
|
|
|
|
mock_new_invoice.return_value = (True,
|
|
|
|
new_invoice(1, InvoiceStatus.pending,
|
|
|
|
bid))
|
|
|
|
|
|
|
|
rv = upload_test_file(client, msg, bid)
|
|
|
|
assert rv.status_code == HTTPStatus.OK
|
|
|
|
check_upload(rv.get_json()['uuid'], msg)
|
|
|
|
check_invoice(rv.get_json()['lightning_invoice'], rv.get_json()['uuid'])
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_text_msg_upload(mock_new_invoice, client):
|
|
|
|
n_bytes = 500
|
|
|
|
bid = bidding.get_min_bid(n_bytes)
|
|
|
|
msg = rnd_string(n_bytes)
|
|
|
|
mock_new_invoice.return_value = (True,
|
|
|
|
new_invoice(1, InvoiceStatus.pending,
|
|
|
|
bid))
|
|
|
|
rv = client.post('/order', data={'bid': bid, 'message': msg.encode()})
|
|
|
|
assert rv.status_code == HTTPStatus.OK
|
|
|
|
check_upload(rv.get_json()['uuid'], msg)
|
|
|
|
check_invoice(rv.get_json()['lightning_invoice'], rv.get_json()['uuid'])
|
|
|
|
|
|
|
|
|
|
|
|
def test_uploaded_file_too_small(client):
|
|
|
|
n_bytes = 0
|
|
|
|
rv = place_order(client, n_bytes)
|
|
|
|
assert_error(rv.get_json(), 'MESSAGE_FILE_TOO_SMALL')
|
|
|
|
assert rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
|
|
|
|
|
2023-02-10 14:57:45 -03:00
|
|
|
# Patch the max sizes on each channel to run the test faster
|
|
|
|
@patch(
|
|
|
|
'constants.CHANNEL_INFO', {
|
|
|
|
constants.USER_CHANNEL:
|
|
|
|
constants.ChannelInfo('transmissions', ['get', 'post', 'delete'], 1000,
|
|
|
|
1000),
|
|
|
|
constants.BTC_SRC_CHANNEL:
|
|
|
|
constants.ChannelInfo('btc-src', ['get'], 500, 1500),
|
|
|
|
})
|
2021-07-20 11:00:49 -03:00
|
|
|
def test_uploaded_file_too_large(client):
|
2023-02-10 14:57:45 -03:00
|
|
|
|
|
|
|
max_size_user_chan = 1000
|
|
|
|
max_size_btc_src_chan = 1500
|
|
|
|
n_bytes = max_size_user_chan + 1
|
2023-02-06 12:56:10 -03:00
|
|
|
rv = place_order(client, n_bytes)
|
|
|
|
assert_error(rv.get_json(), 'MESSAGE_FILE_TOO_LARGE')
|
|
|
|
assert rv.status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE
|
|
|
|
|
2023-02-10 14:57:45 -03:00
|
|
|
# The limit is different per channel. For instance, the above size should
|
2023-02-06 12:56:10 -03:00
|
|
|
# work on the btc-src channel.
|
|
|
|
rv = place_order(client,
|
|
|
|
n_bytes,
|
|
|
|
channel=constants.BTC_SRC_CHANNEL,
|
|
|
|
admin=True)
|
|
|
|
assert rv.status_code == HTTPStatus.OK
|
|
|
|
|
2023-02-10 14:57:45 -03:00
|
|
|
n_bytes = max_size_btc_src_chan + 1
|
2021-07-20 11:00:49 -03:00
|
|
|
rv = place_order(client, n_bytes)
|
|
|
|
assert_error(rv.get_json(), 'MESSAGE_FILE_TOO_LARGE')
|
|
|
|
assert rv.status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_uploaded_file_max_size(mock_new_invoice, client):
|
2023-02-06 12:56:10 -03:00
|
|
|
n_bytes = constants.DEFAULT_MAX_MESSAGE_SIZE
|
2021-07-20 11:00:49 -03:00
|
|
|
mock_new_invoice.return_value = (True,
|
|
|
|
new_invoice(1, InvoiceStatus.pending,
|
|
|
|
bidding.get_min_bid(n_bytes)))
|
|
|
|
rv = place_order(client, n_bytes)
|
|
|
|
assert rv.status_code == HTTPStatus.OK
|
|
|
|
|
|
|
|
|
|
|
|
def test_uploaded_text_msg_too_large(client):
|
|
|
|
n_bytes = constants.MAX_TEXT_MSG_LEN + 1
|
|
|
|
bid = bidding.get_min_bid(n_bytes)
|
|
|
|
msg = rnd_string(n_bytes)
|
|
|
|
rv = client.post('/order', data={'bid': bid, 'message': msg.encode()})
|
|
|
|
assert rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
|
|
|
|
|
Support multiple parallel logical message channels
The same server now can handle multiple logical channels, on which the
transmitter logic runs independently. That is, while previously a single
message would be in transmitting state at a time, now multiple messages
can be in transmitting state as long as they belong to distinct logical
channels.
The supported channels each have different permissions. The user channel
is where users can post, get, and delete messages as needed. In
contrast, the other channels do not grant all permissions to users. Some
are read-only (users can get but not post) and there is a channel (the
auth channel) on which users have no permissions (neither get nor post).
For the channels on which users do not have all permissions (get, post,
and delete), this patch adds admin-specific routes, which are prefixed
by /admin/. The /admin/ route is protected via SSL in production and
allows the admin host to send GET/POST/DELETE requests normally. Hence,
for instance, the admin host can post a message on the auth channel
(with POST /admin/order) and read it (with GET /admin/order) for
transmission over satellite, whereas regulars cannot. With this scheme,
the auth channel messages are accessible exclusively over satellite (and
not over the internet).
The admin routes were added to the following endpoints:
- /order/<uuid> (GET and DELETE requests)
- /order (POST request)
- /orders/<state> (GET request)
- /message/<tx_seq_num> (GET request)
The messages posted by the admin are not paid, so this patch removes the
requirement of invoice generation and payment. Only paid orders now
generate an invoice. Thus, the POST request to the /order/ endpoint does
not return an invoice for non-paid (admin-only) messages.
Also, this patch updates the queue page to display the orders separately
for each channel. The query string channel parameter determines which
channel the page shows.
Finally, this patch updates the events published into the Redis db on
transmission. The event includes the corresponding logical channel so
that SSE events can be subscribed independently for each channel.
2023-01-31 18:14:55 -03:00
|
|
|
def test_post_order_invalid_channel(client):
|
|
|
|
n_bytes = 10
|
|
|
|
bid = bidding.get_min_bid(n_bytes)
|
|
|
|
msg = rnd_string(n_bytes)
|
|
|
|
rv = client.post('/order',
|
|
|
|
data={
|
|
|
|
'bid': bid,
|
|
|
|
'message': msg.encode(),
|
|
|
|
'channel': 10
|
|
|
|
})
|
|
|
|
assert rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
assert 'channel' in rv.get_json()
|
|
|
|
assert "Must be one of" in rv.get_json()['channel'][0]
|
|
|
|
|
|
|
|
|
2021-07-20 11:00:49 -03:00
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_uploaded_text_msg_max_size(mock_new_invoice, client):
|
|
|
|
n_bytes = constants.MAX_TEXT_MSG_LEN
|
|
|
|
bid = bidding.get_min_bid(n_bytes)
|
|
|
|
mock_new_invoice.return_value = (True,
|
|
|
|
new_invoice(1, InvoiceStatus.pending,
|
|
|
|
bid))
|
|
|
|
bid = bidding.get_min_bid(n_bytes)
|
|
|
|
msg = rnd_string(n_bytes)
|
|
|
|
rv = client.post('/order', data={'bid': bid, 'message': msg.encode()})
|
|
|
|
assert rv.status_code == HTTPStatus.OK
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_both_msg_and_file_provided(mock_new_invoice, client):
|
|
|
|
n_bytes = 500
|
|
|
|
bid = bidding.get_min_bid(n_bytes)
|
|
|
|
msg = rnd_string(n_bytes)
|
|
|
|
mock_new_invoice.return_value = (True,
|
|
|
|
new_invoice(1, InvoiceStatus.pending,
|
|
|
|
bid))
|
|
|
|
rv = client.post('/order',
|
|
|
|
data={
|
|
|
|
'bid': bid,
|
|
|
|
'message': msg.encode(),
|
|
|
|
'file': (io.BytesIO(msg.encode()), 'testfile')
|
|
|
|
})
|
|
|
|
assert rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
|
|
|
|
|
|
|
|
def test_negative_bid(client):
|
|
|
|
n_bytes = 500
|
|
|
|
bid = -1
|
|
|
|
msg = rnd_string(n_bytes)
|
|
|
|
rv = upload_test_file(client, msg, bid)
|
|
|
|
assert 'bid' in rv.get_json()
|
|
|
|
assert rv.get_json()['bid'][0] == 'Must be greater than or equal to 0.'
|
|
|
|
assert rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
|
|
|
|
|
|
|
|
def test_bid_too_low(client):
|
|
|
|
n_bytes = 1000
|
|
|
|
bid = 1051
|
|
|
|
msg = rnd_string(n_bytes)
|
|
|
|
rv = upload_test_file(client, msg, bid)
|
|
|
|
assert_error(rv.get_json(), 'BID_TOO_SMALL')
|
|
|
|
assert rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
|
|
|
|
|
|
|
|
def test_order_without_message(client):
|
|
|
|
n_bytes = 500
|
|
|
|
bid = bidding.get_min_bid(n_bytes)
|
|
|
|
rv = client.post('/order', data={'bid': bid})
|
|
|
|
assert_error(rv.get_json(), 'MESSAGE_MISSING')
|
|
|
|
assert rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_invoice_generation_failure(mock_new_invoice, client):
|
|
|
|
mock_new_invoice.return_value =\
|
|
|
|
(False,
|
|
|
|
get_http_error_resp('LIGHTNING_CHARGE_INVOICE_ERROR'))
|
|
|
|
n_bytes = 500
|
|
|
|
rv = place_order(client, n_bytes)
|
|
|
|
assert rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
mock_new_invoice.return_value =\
|
|
|
|
(False,
|
|
|
|
get_http_error_resp(
|
|
|
|
'LIGHTNING_CHARGE_WEBHOOK_REGISTRATION_ERROR'
|
|
|
|
))
|
|
|
|
rv = place_order(client, n_bytes)
|
|
|
|
assert rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_get_order(mock_new_invoice, client):
|
|
|
|
json_response = generate_test_order(mock_new_invoice, client)
|
|
|
|
uuid = json_response['uuid']
|
Support multiple parallel logical message channels
The same server now can handle multiple logical channels, on which the
transmitter logic runs independently. That is, while previously a single
message would be in transmitting state at a time, now multiple messages
can be in transmitting state as long as they belong to distinct logical
channels.
The supported channels each have different permissions. The user channel
is where users can post, get, and delete messages as needed. In
contrast, the other channels do not grant all permissions to users. Some
are read-only (users can get but not post) and there is a channel (the
auth channel) on which users have no permissions (neither get nor post).
For the channels on which users do not have all permissions (get, post,
and delete), this patch adds admin-specific routes, which are prefixed
by /admin/. The /admin/ route is protected via SSL in production and
allows the admin host to send GET/POST/DELETE requests normally. Hence,
for instance, the admin host can post a message on the auth channel
(with POST /admin/order) and read it (with GET /admin/order) for
transmission over satellite, whereas regulars cannot. With this scheme,
the auth channel messages are accessible exclusively over satellite (and
not over the internet).
The admin routes were added to the following endpoints:
- /order/<uuid> (GET and DELETE requests)
- /order (POST request)
- /orders/<state> (GET request)
- /message/<tx_seq_num> (GET request)
The messages posted by the admin are not paid, so this patch removes the
requirement of invoice generation and payment. Only paid orders now
generate an invoice. Thus, the POST request to the /order/ endpoint does
not return an invoice for non-paid (admin-only) messages.
Also, this patch updates the queue page to display the orders separately
for each channel. The query string channel parameter determines which
channel the page shows.
Finally, this patch updates the events published into the Redis db on
transmission. The event includes the corresponding logical channel so
that SSE events can be subscribed independently for each channel.
2023-01-31 18:14:55 -03:00
|
|
|
auth_token = json_response['auth_token']
|
2021-07-20 11:00:49 -03:00
|
|
|
|
Support multiple parallel logical message channels
The same server now can handle multiple logical channels, on which the
transmitter logic runs independently. That is, while previously a single
message would be in transmitting state at a time, now multiple messages
can be in transmitting state as long as they belong to distinct logical
channels.
The supported channels each have different permissions. The user channel
is where users can post, get, and delete messages as needed. In
contrast, the other channels do not grant all permissions to users. Some
are read-only (users can get but not post) and there is a channel (the
auth channel) on which users have no permissions (neither get nor post).
For the channels on which users do not have all permissions (get, post,
and delete), this patch adds admin-specific routes, which are prefixed
by /admin/. The /admin/ route is protected via SSL in production and
allows the admin host to send GET/POST/DELETE requests normally. Hence,
for instance, the admin host can post a message on the auth channel
(with POST /admin/order) and read it (with GET /admin/order) for
transmission over satellite, whereas regulars cannot. With this scheme,
the auth channel messages are accessible exclusively over satellite (and
not over the internet).
The admin routes were added to the following endpoints:
- /order/<uuid> (GET and DELETE requests)
- /order (POST request)
- /orders/<state> (GET request)
- /message/<tx_seq_num> (GET request)
The messages posted by the admin are not paid, so this patch removes the
requirement of invoice generation and payment. Only paid orders now
generate an invoice. Thus, the POST request to the /order/ endpoint does
not return an invoice for non-paid (admin-only) messages.
Also, this patch updates the queue page to display the orders separately
for each channel. The query string channel parameter determines which
channel the page shows.
Finally, this patch updates the events published into the Redis db on
transmission. The event includes the corresponding logical channel so
that SSE events can be subscribed independently for each channel.
2023-01-31 18:14:55 -03:00
|
|
|
get_rv = client.get(f'/order/{uuid}', headers={'X-Auth-Token': auth_token})
|
2021-07-20 11:00:49 -03:00
|
|
|
get_json_resp = get_rv.get_json()
|
|
|
|
assert get_rv.status_code == HTTPStatus.OK
|
|
|
|
assert get_json_resp['uuid'] == uuid
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
2021-07-21 14:43:06 -03:00
|
|
|
def test_get_order_auth_token_as_form_param(mock_new_invoice, client):
|
2021-07-20 11:00:49 -03:00
|
|
|
json_response = generate_test_order(mock_new_invoice, client)
|
|
|
|
uuid = json_response['uuid']
|
Support multiple parallel logical message channels
The same server now can handle multiple logical channels, on which the
transmitter logic runs independently. That is, while previously a single
message would be in transmitting state at a time, now multiple messages
can be in transmitting state as long as they belong to distinct logical
channels.
The supported channels each have different permissions. The user channel
is where users can post, get, and delete messages as needed. In
contrast, the other channels do not grant all permissions to users. Some
are read-only (users can get but not post) and there is a channel (the
auth channel) on which users have no permissions (neither get nor post).
For the channels on which users do not have all permissions (get, post,
and delete), this patch adds admin-specific routes, which are prefixed
by /admin/. The /admin/ route is protected via SSL in production and
allows the admin host to send GET/POST/DELETE requests normally. Hence,
for instance, the admin host can post a message on the auth channel
(with POST /admin/order) and read it (with GET /admin/order) for
transmission over satellite, whereas regulars cannot. With this scheme,
the auth channel messages are accessible exclusively over satellite (and
not over the internet).
The admin routes were added to the following endpoints:
- /order/<uuid> (GET and DELETE requests)
- /order (POST request)
- /orders/<state> (GET request)
- /message/<tx_seq_num> (GET request)
The messages posted by the admin are not paid, so this patch removes the
requirement of invoice generation and payment. Only paid orders now
generate an invoice. Thus, the POST request to the /order/ endpoint does
not return an invoice for non-paid (admin-only) messages.
Also, this patch updates the queue page to display the orders separately
for each channel. The query string channel parameter determines which
channel the page shows.
Finally, this patch updates the events published into the Redis db on
transmission. The event includes the corresponding logical channel so
that SSE events can be subscribed independently for each channel.
2023-01-31 18:14:55 -03:00
|
|
|
auth_token = json_response['auth_token']
|
2021-07-20 11:00:49 -03:00
|
|
|
|
Support multiple parallel logical message channels
The same server now can handle multiple logical channels, on which the
transmitter logic runs independently. That is, while previously a single
message would be in transmitting state at a time, now multiple messages
can be in transmitting state as long as they belong to distinct logical
channels.
The supported channels each have different permissions. The user channel
is where users can post, get, and delete messages as needed. In
contrast, the other channels do not grant all permissions to users. Some
are read-only (users can get but not post) and there is a channel (the
auth channel) on which users have no permissions (neither get nor post).
For the channels on which users do not have all permissions (get, post,
and delete), this patch adds admin-specific routes, which are prefixed
by /admin/. The /admin/ route is protected via SSL in production and
allows the admin host to send GET/POST/DELETE requests normally. Hence,
for instance, the admin host can post a message on the auth channel
(with POST /admin/order) and read it (with GET /admin/order) for
transmission over satellite, whereas regulars cannot. With this scheme,
the auth channel messages are accessible exclusively over satellite (and
not over the internet).
The admin routes were added to the following endpoints:
- /order/<uuid> (GET and DELETE requests)
- /order (POST request)
- /orders/<state> (GET request)
- /message/<tx_seq_num> (GET request)
The messages posted by the admin are not paid, so this patch removes the
requirement of invoice generation and payment. Only paid orders now
generate an invoice. Thus, the POST request to the /order/ endpoint does
not return an invoice for non-paid (admin-only) messages.
Also, this patch updates the queue page to display the orders separately
for each channel. The query string channel parameter determines which
channel the page shows.
Finally, this patch updates the events published into the Redis db on
transmission. The event includes the corresponding logical channel so
that SSE events can be subscribed independently for each channel.
2023-01-31 18:14:55 -03:00
|
|
|
get_rv = client.get(f'/order/{uuid}', data={'auth_token': auth_token})
|
2021-07-20 11:00:49 -03:00
|
|
|
get_json_resp = get_rv.get_json()
|
|
|
|
assert get_rv.status_code == HTTPStatus.OK
|
|
|
|
assert get_json_resp['uuid'] == uuid
|
|
|
|
|
|
|
|
|
2021-07-21 14:43:06 -03:00
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_get_order_auth_token_as_query_param(mock_new_invoice, client):
|
|
|
|
json_response = generate_test_order(mock_new_invoice, client)
|
|
|
|
uuid = json_response['uuid']
|
|
|
|
auth_token = json_response['auth_token']
|
Support multiple parallel logical message channels
The same server now can handle multiple logical channels, on which the
transmitter logic runs independently. That is, while previously a single
message would be in transmitting state at a time, now multiple messages
can be in transmitting state as long as they belong to distinct logical
channels.
The supported channels each have different permissions. The user channel
is where users can post, get, and delete messages as needed. In
contrast, the other channels do not grant all permissions to users. Some
are read-only (users can get but not post) and there is a channel (the
auth channel) on which users have no permissions (neither get nor post).
For the channels on which users do not have all permissions (get, post,
and delete), this patch adds admin-specific routes, which are prefixed
by /admin/. The /admin/ route is protected via SSL in production and
allows the admin host to send GET/POST/DELETE requests normally. Hence,
for instance, the admin host can post a message on the auth channel
(with POST /admin/order) and read it (with GET /admin/order) for
transmission over satellite, whereas regulars cannot. With this scheme,
the auth channel messages are accessible exclusively over satellite (and
not over the internet).
The admin routes were added to the following endpoints:
- /order/<uuid> (GET and DELETE requests)
- /order (POST request)
- /orders/<state> (GET request)
- /message/<tx_seq_num> (GET request)
The messages posted by the admin are not paid, so this patch removes the
requirement of invoice generation and payment. Only paid orders now
generate an invoice. Thus, the POST request to the /order/ endpoint does
not return an invoice for non-paid (admin-only) messages.
Also, this patch updates the queue page to display the orders separately
for each channel. The query string channel parameter determines which
channel the page shows.
Finally, this patch updates the events published into the Redis db on
transmission. The event includes the corresponding logical channel so
that SSE events can be subscribed independently for each channel.
2023-01-31 18:14:55 -03:00
|
|
|
|
2021-07-21 14:43:06 -03:00
|
|
|
get_rv = client.get(f'/order/{uuid}?auth_token={auth_token}')
|
|
|
|
get_json_resp = get_rv.get_json()
|
|
|
|
assert get_rv.status_code == HTTPStatus.OK
|
|
|
|
assert get_json_resp['uuid'] == uuid
|
|
|
|
|
|
|
|
|
2021-07-20 11:00:49 -03:00
|
|
|
def test_get_nonexistent_order(client):
|
|
|
|
uuid = str(uuid4())
|
|
|
|
rv = client.get(f'/order/{uuid}',
|
|
|
|
headers={'X-Auth-Token': 'test-auth-token'})
|
|
|
|
assert_error(rv.get_json(), 'ORDER_NOT_FOUND')
|
|
|
|
assert rv.status_code == HTTPStatus.NOT_FOUND
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_get_order_wrong_auth_token(mock_new_invoice, client):
|
|
|
|
uuid = generate_test_order(mock_new_invoice, client)['uuid']
|
|
|
|
|
|
|
|
get_rv = client.get(f'/order/{uuid}',
|
|
|
|
headers={'X-Auth-Token': 'wrong-auth-token'})
|
|
|
|
assert_error(get_rv.get_json(), 'INVALID_AUTH_TOKEN')
|
|
|
|
assert get_rv.status_code == HTTPStatus.UNAUTHORIZED
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_get_order_missing_auth_token(mock_new_invoice, client):
|
|
|
|
uuid = generate_test_order(mock_new_invoice, client)['uuid']
|
|
|
|
|
|
|
|
get_rv = client.get(f'/order/{uuid}')
|
|
|
|
assert_error(get_rv.get_json(), 'INVALID_AUTH_TOKEN')
|
|
|
|
assert get_rv.status_code == HTTPStatus.UNAUTHORIZED
|
|
|
|
|
|
|
|
|
Support multiple parallel logical message channels
The same server now can handle multiple logical channels, on which the
transmitter logic runs independently. That is, while previously a single
message would be in transmitting state at a time, now multiple messages
can be in transmitting state as long as they belong to distinct logical
channels.
The supported channels each have different permissions. The user channel
is where users can post, get, and delete messages as needed. In
contrast, the other channels do not grant all permissions to users. Some
are read-only (users can get but not post) and there is a channel (the
auth channel) on which users have no permissions (neither get nor post).
For the channels on which users do not have all permissions (get, post,
and delete), this patch adds admin-specific routes, which are prefixed
by /admin/. The /admin/ route is protected via SSL in production and
allows the admin host to send GET/POST/DELETE requests normally. Hence,
for instance, the admin host can post a message on the auth channel
(with POST /admin/order) and read it (with GET /admin/order) for
transmission over satellite, whereas regulars cannot. With this scheme,
the auth channel messages are accessible exclusively over satellite (and
not over the internet).
The admin routes were added to the following endpoints:
- /order/<uuid> (GET and DELETE requests)
- /order (POST request)
- /orders/<state> (GET request)
- /message/<tx_seq_num> (GET request)
The messages posted by the admin are not paid, so this patch removes the
requirement of invoice generation and payment. Only paid orders now
generate an invoice. Thus, the POST request to the /order/ endpoint does
not return an invoice for non-paid (admin-only) messages.
Also, this patch updates the queue page to display the orders separately
for each channel. The query string channel parameter determines which
channel the page shows.
Finally, this patch updates the events published into the Redis db on
transmission. The event includes the corresponding logical channel so
that SSE events can be subscribed independently for each channel.
2023-01-31 18:14:55 -03:00
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_get_admin_order(mock_new_invoice, client):
|
|
|
|
# Post order on a channel that forbids users from fetching messages
|
|
|
|
json_response = generate_test_order(mock_new_invoice,
|
|
|
|
client,
|
|
|
|
channel=constants.AUTH_CHANNEL,
|
|
|
|
admin=True)
|
|
|
|
uuid = json_response['uuid']
|
|
|
|
auth_token = json_response['auth_token']
|
|
|
|
|
|
|
|
# Getting through the normal route should fail
|
|
|
|
get_rv = client.get(f'/order/{uuid}', headers={'X-Auth-Token': auth_token})
|
|
|
|
assert get_rv.status_code == HTTPStatus.UNAUTHORIZED
|
|
|
|
assert_error(get_rv.get_json(), 'ORDER_CHANNEL_UNAUTHORIZED_OP')
|
|
|
|
|
|
|
|
# Getting through the admin route should work
|
|
|
|
get_rv = client.get(f'/admin/order/{uuid}',
|
|
|
|
headers={'X-Auth-Token': auth_token})
|
|
|
|
get_json_resp = get_rv.get_json()
|
|
|
|
assert get_rv.status_code == HTTPStatus.OK
|
|
|
|
assert get_json_resp['uuid'] == uuid
|
|
|
|
|
|
|
|
|
2021-07-20 11:00:49 -03:00
|
|
|
def test_adjust_bids(client):
|
|
|
|
# if the values like bid, unpaid_bid, bid_per_byte are wrong or become
|
|
|
|
# obsolete due to changes in the invoices, the adjust_bids function should
|
|
|
|
# be able to fix them all
|
|
|
|
n_bytes = 1000
|
|
|
|
paid_bid = 1000
|
|
|
|
unpaid_bid = 10000
|
|
|
|
order = Order(id=1,
|
|
|
|
uuid='a-b-c',
|
|
|
|
bid=123,
|
|
|
|
message_size=n_bytes,
|
|
|
|
bid_per_byte=56.0,
|
|
|
|
message_digest='abcd',
|
|
|
|
status=OrderStatus.pending.value,
|
|
|
|
unpaid_bid=2345,
|
|
|
|
invoices=[
|
|
|
|
new_invoice(1, InvoiceStatus.pending,
|
|
|
|
int(0.4 * unpaid_bid)),
|
|
|
|
new_invoice(1, InvoiceStatus.pending,
|
|
|
|
int(0.6 * unpaid_bid)),
|
|
|
|
new_invoice(1, InvoiceStatus.paid, int(0.3 * paid_bid)),
|
|
|
|
new_invoice(1, InvoiceStatus.paid, int(0.7 * paid_bid))
|
|
|
|
])
|
|
|
|
assert _paid_invoices_total(order) == paid_bid
|
|
|
|
assert _unpaid_invoices_total(order) == unpaid_bid
|
|
|
|
adjust_bids(order)
|
|
|
|
assert order.bid == paid_bid
|
|
|
|
assert order.unpaid_bid == unpaid_bid
|
|
|
|
expected_bid_per_byte = paid_bid / (n_bytes + 52) # w/ 52 overhead bytes
|
2021-07-06 11:44:10 -03:00
|
|
|
assert order.bid_per_byte == expected_bid_per_byte
|
2021-07-20 11:00:49 -03:00
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_negative_bid_increase_error(mock_new_invoice, client):
|
|
|
|
initial_bid = 1000
|
|
|
|
json_response = generate_test_order(mock_new_invoice,
|
|
|
|
client,
|
|
|
|
bid=initial_bid)
|
|
|
|
uuid = json_response['uuid']
|
|
|
|
auth_token = json_response['auth_token']
|
|
|
|
|
|
|
|
# Bump the bid with a negative value
|
|
|
|
bump_rv = client.post(f'/order/{uuid}/bump',
|
|
|
|
data={
|
|
|
|
'bid_increase': -1,
|
|
|
|
},
|
|
|
|
headers={'X-Auth-Token': auth_token})
|
|
|
|
assert bump_rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_bump_order(mock_new_invoice, client):
|
|
|
|
initial_bid = 1000
|
|
|
|
json_response = generate_test_order(mock_new_invoice,
|
|
|
|
client,
|
|
|
|
bid=initial_bid)
|
|
|
|
uuid = json_response['uuid']
|
|
|
|
auth_token = json_response['auth_token']
|
|
|
|
|
|
|
|
get_rv = client.get(f'/order/{uuid}', headers={'X-Auth-Token': auth_token})
|
|
|
|
assert get_rv.status_code == HTTPStatus.OK
|
|
|
|
get_json_resp = get_rv.get_json()
|
|
|
|
assert get_json_resp['unpaid_bid'] == initial_bid
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
assert len(db_order.invoices) == 1
|
|
|
|
|
|
|
|
# Bump the bid on the existing order
|
|
|
|
bid_increase = 2500
|
|
|
|
mock_new_invoice.return_value = (True,
|
|
|
|
new_invoice(1, InvoiceStatus.pending,
|
|
|
|
bid_increase))
|
|
|
|
bump_rv = client.post(f'/order/{uuid}/bump',
|
|
|
|
data={
|
|
|
|
'bid_increase': bid_increase,
|
|
|
|
},
|
|
|
|
headers={'X-Auth-Token': auth_token})
|
|
|
|
assert bump_rv.status_code == HTTPStatus.OK
|
|
|
|
|
|
|
|
# Get the order and check if the bid was bumped successfully
|
|
|
|
get_rv = client.get(f'/order/{uuid}', headers={'X-Auth-Token': auth_token})
|
|
|
|
assert get_rv.status_code == HTTPStatus.OK
|
|
|
|
get_json_resp = get_rv.get_json()
|
|
|
|
assert get_json_resp['unpaid_bid'] == initial_bid + bid_increase
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
assert len(db_order.invoices) == 2
|
|
|
|
|
|
|
|
# If a bump request fails, the order should stay untouched
|
|
|
|
second_bid_increase = 3000
|
|
|
|
mock_new_invoice.return_value = \
|
|
|
|
(False,
|
|
|
|
get_http_error_resp('LIGHTNING_CHARGE_INVOICE_ERROR'))
|
|
|
|
bump_rv = client.post(f'/order/{uuid}/bump',
|
|
|
|
data={
|
|
|
|
'bid_increase': second_bid_increase,
|
|
|
|
},
|
|
|
|
headers={'X-Auth-Token': auth_token})
|
|
|
|
assert bump_rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
|
|
|
|
# Get the order and verify the second bid increase (which failed) was
|
|
|
|
# completely ignored
|
|
|
|
get_rv = client.get(f'/order/{uuid}', headers={'X-Auth-Token': auth_token})
|
|
|
|
assert get_rv.status_code == HTTPStatus.OK
|
|
|
|
get_json_resp = get_rv.get_json()
|
|
|
|
assert get_json_resp['unpaid_bid'] == initial_bid + bid_increase
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
assert len(db_order.invoices) == 2
|
|
|
|
|
|
|
|
|
2021-12-21 16:11:45 -03:00
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_bump_transmitted_order(mock_new_invoice, client):
|
|
|
|
initial_bid = 1000
|
|
|
|
json_response = generate_test_order(mock_new_invoice,
|
|
|
|
client,
|
|
|
|
bid=initial_bid)
|
|
|
|
uuid = json_response['uuid']
|
|
|
|
auth_token = json_response['auth_token']
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
pay_invoice(db_order.invoices[0], client)
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
get_rv = client.get(f'/order/{uuid}', headers={'X-Auth-Token': auth_token})
|
|
|
|
assert get_rv.status_code == HTTPStatus.OK
|
|
|
|
get_json_resp = get_rv.get_json()
|
|
|
|
assert get_json_resp['unpaid_bid'] == 0
|
|
|
|
assert get_json_resp['bid'] == initial_bid
|
|
|
|
assert get_json_resp['status'] == OrderStatus.transmitting.name
|
|
|
|
|
|
|
|
# Since the order is already in transmitting state, a bump request should
|
|
|
|
# return error
|
|
|
|
bid_increase = 2500
|
|
|
|
mock_new_invoice.return_value = (True,
|
|
|
|
new_invoice(1, InvoiceStatus.pending,
|
|
|
|
bid_increase))
|
|
|
|
bump_rv = client.post(f'/order/{uuid}/bump',
|
|
|
|
data={
|
|
|
|
'bid_increase': bid_increase,
|
|
|
|
},
|
|
|
|
headers={'X-Auth-Token': auth_token})
|
|
|
|
assert bump_rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
assert_error(bump_rv.get_json(), 'ORDER_BUMP_ERROR')
|
|
|
|
|
|
|
|
|
Support multiple parallel logical message channels
The same server now can handle multiple logical channels, on which the
transmitter logic runs independently. That is, while previously a single
message would be in transmitting state at a time, now multiple messages
can be in transmitting state as long as they belong to distinct logical
channels.
The supported channels each have different permissions. The user channel
is where users can post, get, and delete messages as needed. In
contrast, the other channels do not grant all permissions to users. Some
are read-only (users can get but not post) and there is a channel (the
auth channel) on which users have no permissions (neither get nor post).
For the channels on which users do not have all permissions (get, post,
and delete), this patch adds admin-specific routes, which are prefixed
by /admin/. The /admin/ route is protected via SSL in production and
allows the admin host to send GET/POST/DELETE requests normally. Hence,
for instance, the admin host can post a message on the auth channel
(with POST /admin/order) and read it (with GET /admin/order) for
transmission over satellite, whereas regulars cannot. With this scheme,
the auth channel messages are accessible exclusively over satellite (and
not over the internet).
The admin routes were added to the following endpoints:
- /order/<uuid> (GET and DELETE requests)
- /order (POST request)
- /orders/<state> (GET request)
- /message/<tx_seq_num> (GET request)
The messages posted by the admin are not paid, so this patch removes the
requirement of invoice generation and payment. Only paid orders now
generate an invoice. Thus, the POST request to the /order/ endpoint does
not return an invoice for non-paid (admin-only) messages.
Also, this patch updates the queue page to display the orders separately
for each channel. The query string channel parameter determines which
channel the page shows.
Finally, this patch updates the events published into the Redis db on
transmission. The event includes the corresponding logical channel so
that SSE events can be subscribed independently for each channel.
2023-01-31 18:14:55 -03:00
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_bump_non_paid_order(mock_new_invoice, client):
|
|
|
|
# Send order over the gossip channel, which is not paid.
|
|
|
|
initial_bid = 1000
|
|
|
|
json_response = generate_test_order(mock_new_invoice,
|
|
|
|
client,
|
|
|
|
bid=initial_bid,
|
|
|
|
channel=constants.GOSSIP_CHANNEL,
|
|
|
|
admin=True)
|
|
|
|
uuid = json_response['uuid']
|
|
|
|
auth_token = json_response['auth_token']
|
|
|
|
|
|
|
|
# Since the order is not paid, a bump request should not be authorized
|
|
|
|
bid_increase = 2500
|
|
|
|
bump_rv = client.post(f'/order/{uuid}/bump',
|
|
|
|
data={
|
|
|
|
'bid_increase': bid_increase,
|
|
|
|
},
|
|
|
|
headers={'X-Auth-Token': auth_token})
|
|
|
|
assert bump_rv.status_code == HTTPStatus.UNAUTHORIZED
|
|
|
|
assert_error(bump_rv.get_json(), 'ORDER_CHANNEL_UNAUTHORIZED_OP')
|
|
|
|
|
|
|
|
|
2021-07-20 11:00:49 -03:00
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_cancel_order(mock_new_invoice, client):
|
|
|
|
bid = 1000
|
|
|
|
json_response = generate_test_order(mock_new_invoice, client, bid=bid)
|
|
|
|
uuid = json_response['uuid']
|
|
|
|
auth_token = json_response['auth_token']
|
|
|
|
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
assert db_order.status == OrderStatus.pending.value
|
|
|
|
assert db_order.cancelled_at is None
|
|
|
|
|
|
|
|
# Only pending and paid orders can be cancelled
|
|
|
|
for status in OrderStatus:
|
|
|
|
db_order.status = status.value
|
|
|
|
db.session.commit()
|
|
|
|
if status in [OrderStatus.paid, OrderStatus.pending]:
|
|
|
|
delete_rv = client.delete(f'/order/{uuid}',
|
|
|
|
headers={'X-Auth-Token': auth_token})
|
|
|
|
assert delete_rv.status_code == HTTPStatus.OK
|
|
|
|
else:
|
|
|
|
delete_rv = client.delete(f'/order/{uuid}',
|
|
|
|
headers={'X-Auth-Token': auth_token})
|
|
|
|
assert delete_rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
assert_error(delete_rv.get_json(), 'ORDER_CANCELLATION_ERROR')
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_cancel_order_twice(mock_new_invoice, client):
|
|
|
|
bid = 1000
|
|
|
|
json_response = generate_test_order(mock_new_invoice, client, bid=bid)
|
|
|
|
uuid = json_response['uuid']
|
|
|
|
auth_token = json_response['auth_token']
|
|
|
|
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
assert db_order.status == OrderStatus.pending.value
|
|
|
|
assert db_order.cancelled_at is None
|
|
|
|
message_path = os.path.join(constants.MSG_STORE_PATH, uuid)
|
|
|
|
assert os.path.exists(message_path)
|
|
|
|
|
|
|
|
# Cancel the order
|
|
|
|
delete_rv = client.delete(f'/order/{uuid}',
|
|
|
|
headers={'X-Auth-Token': auth_token})
|
|
|
|
assert delete_rv.status_code == HTTPStatus.OK
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
assert db_order.status == OrderStatus.cancelled.value
|
|
|
|
assert db_order.cancelled_at is not None
|
|
|
|
assert not os.path.exists(message_path)
|
|
|
|
|
|
|
|
# Try to cancel the order again
|
|
|
|
delete_rv = client.delete(f'/order/{uuid}',
|
|
|
|
headers={'X-Auth-Token': auth_token})
|
|
|
|
assert delete_rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
assert_error(delete_rv.get_json(), 'ORDER_CANCELLATION_ERROR')
|
|
|
|
|
|
|
|
|
|
|
|
def test_cancel_non_existing_order(client):
|
|
|
|
delete_rv = client.delete('/order/13245',
|
|
|
|
headers={'X-Auth-Token': 'token'})
|
|
|
|
assert delete_rv.status_code == HTTPStatus.NOT_FOUND
|
|
|
|
assert_error(delete_rv.get_json(), 'ORDER_NOT_FOUND')
|
|
|
|
|
|
|
|
|
Support multiple parallel logical message channels
The same server now can handle multiple logical channels, on which the
transmitter logic runs independently. That is, while previously a single
message would be in transmitting state at a time, now multiple messages
can be in transmitting state as long as they belong to distinct logical
channels.
The supported channels each have different permissions. The user channel
is where users can post, get, and delete messages as needed. In
contrast, the other channels do not grant all permissions to users. Some
are read-only (users can get but not post) and there is a channel (the
auth channel) on which users have no permissions (neither get nor post).
For the channels on which users do not have all permissions (get, post,
and delete), this patch adds admin-specific routes, which are prefixed
by /admin/. The /admin/ route is protected via SSL in production and
allows the admin host to send GET/POST/DELETE requests normally. Hence,
for instance, the admin host can post a message on the auth channel
(with POST /admin/order) and read it (with GET /admin/order) for
transmission over satellite, whereas regulars cannot. With this scheme,
the auth channel messages are accessible exclusively over satellite (and
not over the internet).
The admin routes were added to the following endpoints:
- /order/<uuid> (GET and DELETE requests)
- /order (POST request)
- /orders/<state> (GET request)
- /message/<tx_seq_num> (GET request)
The messages posted by the admin are not paid, so this patch removes the
requirement of invoice generation and payment. Only paid orders now
generate an invoice. Thus, the POST request to the /order/ endpoint does
not return an invoice for non-paid (admin-only) messages.
Also, this patch updates the queue page to display the orders separately
for each channel. The query string channel parameter determines which
channel the page shows.
Finally, this patch updates the events published into the Redis db on
transmission. The event includes the corresponding logical channel so
that SSE events can be subscribed independently for each channel.
2023-01-31 18:14:55 -03:00
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_cancel_order_unauthorized_channel_op(mock_new_invoice, client):
|
|
|
|
# Place order on a channel that forbids uses from deleting orders.
|
|
|
|
bid = 1000
|
|
|
|
json_response = generate_test_order(mock_new_invoice,
|
|
|
|
client,
|
|
|
|
bid=bid,
|
|
|
|
channel=constants.GOSSIP_CHANNEL,
|
|
|
|
admin=True)
|
|
|
|
uuid = json_response['uuid']
|
|
|
|
auth_token = json_response['auth_token']
|
|
|
|
|
|
|
|
# Deleting through the regular user endpoint should fail
|
|
|
|
delete_rv = client.delete(f'/order/{uuid}',
|
|
|
|
headers={'X-Auth-Token': auth_token})
|
|
|
|
assert delete_rv.status_code == HTTPStatus.UNAUTHORIZED
|
|
|
|
assert_error(delete_rv.get_json(), 'ORDER_CHANNEL_UNAUTHORIZED_OP')
|
|
|
|
|
|
|
|
# Deleting through the admin endpoint should work (i.e., won't return
|
|
|
|
# unauthorized operation)
|
|
|
|
delete_rv = client.delete(f'/admin/order/{uuid}',
|
|
|
|
headers={'X-Auth-Token': auth_token})
|
|
|
|
# In this case, it hits a cancellation error because the order is already
|
|
|
|
# in transmitting state, given that the gossip channel is auto-paid.
|
|
|
|
assert delete_rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
assert_error(delete_rv.get_json(), 'ORDER_CANCELLATION_ERROR')
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
assert db_order.status == OrderStatus.transmitting.value
|
|
|
|
assert db_order.cancelled_at is None
|
|
|
|
|
|
|
|
|
2021-07-20 11:00:49 -03:00
|
|
|
def test_get_sent_message_for_nonexisting_uuid(client):
|
|
|
|
# Try to get message for a non existing uuid
|
|
|
|
rv = client.get('/order/some-uuid/sent_message')
|
|
|
|
assert rv.status_code == HTTPStatus.NOT_FOUND
|
|
|
|
assert_error(rv.get_json(), 'ORDER_NOT_FOUND')
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_get_sent_message_for_pending_order(mock_new_invoice, client):
|
|
|
|
uuid = generate_test_order(mock_new_invoice, client)['uuid']
|
|
|
|
# The test order has pending status. Hence, fetching via the
|
|
|
|
# /order/{uuid}/sent_message endpoint should fail. Only "sent" and
|
|
|
|
# "transmitting" orders can be requested.
|
|
|
|
rv = client.get(f'/order/{uuid}/sent_message')
|
|
|
|
assert rv.status_code == HTTPStatus.NOT_FOUND
|
|
|
|
assert_error(rv.get_json(), 'ORDER_NOT_FOUND')
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
@pytest.mark.parametrize("status",
|
|
|
|
[OrderStatus.sent, OrderStatus.transmitting])
|
|
|
|
def test_get_sent_message_from_uuid(mock_new_invoice, client, status):
|
|
|
|
uuid = generate_test_order(mock_new_invoice, client,
|
|
|
|
order_status=status)['uuid']
|
|
|
|
rv = client.get(f'/order/{uuid}/sent_message')
|
|
|
|
assert rv.status_code == HTTPStatus.OK
|
|
|
|
received_message = rv.data
|
|
|
|
check_received_message(uuid, received_message)
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_sent_message_for_nonexisting_seq_number(client):
|
|
|
|
# Try to get message for a non existing seq number
|
|
|
|
rv = client.get('/message/1')
|
|
|
|
assert rv.status_code == HTTPStatus.NOT_FOUND
|
|
|
|
assert_error(rv.get_json(), 'SEQUENCE_NUMBER_NOT_FOUND')
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_get_sent_message_by_seq_number_for_paid_order(mock_new_invoice,
|
|
|
|
client):
|
|
|
|
uuid = generate_test_order(mock_new_invoice, client)['uuid']
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
2021-12-21 16:32:30 -03:00
|
|
|
pay_invoice(db_order.invoices[0], client)
|
2021-07-20 11:00:49 -03:00
|
|
|
db.session.commit()
|
|
|
|
|
2021-12-21 16:31:41 -03:00
|
|
|
# Because the invoice was paid, the order should go immediately into
|
|
|
|
# transmitting state and be assigned with sequence number 1.
|
2021-07-20 11:00:49 -03:00
|
|
|
rv = client.get('/message/1')
|
2021-12-21 16:31:41 -03:00
|
|
|
assert rv.status_code == HTTPStatus.OK
|
|
|
|
received_message = rv.data
|
|
|
|
check_received_message(uuid, received_message)
|
2021-07-20 11:00:49 -03:00
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"status",
|
|
|
|
[OrderStatus.sent, OrderStatus.received, OrderStatus.transmitting])
|
|
|
|
def test_get_sent_message_by_seq_number(mock_new_invoice, client, status):
|
|
|
|
uuid = generate_test_order(mock_new_invoice,
|
|
|
|
client,
|
|
|
|
order_status=status,
|
|
|
|
tx_seq_num=1)['uuid']
|
|
|
|
|
|
|
|
# Get order's sent message by seq number
|
|
|
|
rv = client.get('/message/1')
|
|
|
|
assert rv.status_code == HTTPStatus.OK
|
|
|
|
received_message = rv.data
|
|
|
|
check_received_message(uuid, received_message)
|
|
|
|
|
|
|
|
|
Support multiple parallel logical message channels
The same server now can handle multiple logical channels, on which the
transmitter logic runs independently. That is, while previously a single
message would be in transmitting state at a time, now multiple messages
can be in transmitting state as long as they belong to distinct logical
channels.
The supported channels each have different permissions. The user channel
is where users can post, get, and delete messages as needed. In
contrast, the other channels do not grant all permissions to users. Some
are read-only (users can get but not post) and there is a channel (the
auth channel) on which users have no permissions (neither get nor post).
For the channels on which users do not have all permissions (get, post,
and delete), this patch adds admin-specific routes, which are prefixed
by /admin/. The /admin/ route is protected via SSL in production and
allows the admin host to send GET/POST/DELETE requests normally. Hence,
for instance, the admin host can post a message on the auth channel
(with POST /admin/order) and read it (with GET /admin/order) for
transmission over satellite, whereas regulars cannot. With this scheme,
the auth channel messages are accessible exclusively over satellite (and
not over the internet).
The admin routes were added to the following endpoints:
- /order/<uuid> (GET and DELETE requests)
- /order (POST request)
- /orders/<state> (GET request)
- /message/<tx_seq_num> (GET request)
The messages posted by the admin are not paid, so this patch removes the
requirement of invoice generation and payment. Only paid orders now
generate an invoice. Thus, the POST request to the /order/ endpoint does
not return an invoice for non-paid (admin-only) messages.
Also, this patch updates the queue page to display the orders separately
for each channel. The query string channel parameter determines which
channel the page shows.
Finally, this patch updates the events published into the Redis db on
transmission. The event includes the corresponding logical channel so
that SSE events can be subscribed independently for each channel.
2023-01-31 18:14:55 -03:00
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_get_sent_message_by_seq_number_unauthorized_channel_op(
|
|
|
|
mock_new_invoice, client):
|
|
|
|
# Create an order on the auth channel, which forbids GET requests from
|
|
|
|
# users. Make sure those requests fail. And use the /admin/order endpoint
|
|
|
|
# when POSTing the order.
|
|
|
|
uuid = generate_test_order(mock_new_invoice,
|
|
|
|
client,
|
|
|
|
order_status=OrderStatus.sent,
|
|
|
|
tx_seq_num=1,
|
|
|
|
channel=constants.AUTH_CHANNEL,
|
|
|
|
admin=True)['uuid']
|
|
|
|
|
|
|
|
# Reading by sequence number via the regular route should fail
|
|
|
|
rv = client.get('/message/1')
|
|
|
|
assert rv.status_code == HTTPStatus.UNAUTHORIZED
|
|
|
|
assert_error(rv.get_json(), 'ORDER_CHANNEL_UNAUTHORIZED_OP')
|
|
|
|
|
|
|
|
# Reading by sequence number via the admin route should fail
|
|
|
|
rv = client.get('/admin/message/1')
|
|
|
|
assert rv.status_code == HTTPStatus.OK
|
|
|
|
received_message = rv.data
|
|
|
|
check_received_message(uuid, received_message)
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
@pytest.mark.parametrize("channel", constants.CHANNELS)
|
|
|
|
def test_get_sent_message_admin(mock_new_invoice, client, channel):
|
|
|
|
# the admin should be able to get messages from any channel
|
|
|
|
uuid = generate_test_order(mock_new_invoice,
|
|
|
|
client,
|
|
|
|
order_status=OrderStatus.sent,
|
|
|
|
tx_seq_num=1,
|
|
|
|
channel=channel,
|
|
|
|
admin=True)['uuid']
|
|
|
|
|
|
|
|
rv = client.get(f'/admin/message/1?channel={channel}')
|
|
|
|
assert rv.status_code == HTTPStatus.OK
|
|
|
|
received_message = rv.data
|
|
|
|
check_received_message(uuid, received_message)
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
@pytest.mark.parametrize("channel", [
|
|
|
|
constants.GOSSIP_CHANNEL, constants.BTC_SRC_CHANNEL, constants.AUTH_CHANNEL
|
|
|
|
])
|
|
|
|
def test_post_order_unauthorized_channel(mock_new_invoice, client, channel):
|
|
|
|
# users are not authorized to post to some channels
|
|
|
|
n_bytes = 500
|
|
|
|
bid = bidding.get_min_bid(n_bytes)
|
|
|
|
msg = rnd_string(n_bytes)
|
|
|
|
mock_new_invoice.return_value = (True,
|
|
|
|
new_invoice(1, InvoiceStatus.pending,
|
|
|
|
bid))
|
|
|
|
rv = client.post('/order',
|
|
|
|
data={
|
|
|
|
'bid': bid,
|
|
|
|
'message': msg.encode(),
|
|
|
|
'channel': channel
|
|
|
|
})
|
|
|
|
assert rv.status_code == HTTPStatus.UNAUTHORIZED
|
|
|
|
assert_error(rv.get_json(), 'ORDER_CHANNEL_UNAUTHORIZED_OP')
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
@pytest.mark.parametrize("channel", constants.CHANNELS)
|
|
|
|
def test_post_order_admin(mock_new_invoice, client, channel):
|
|
|
|
# the admin should be allowed to POST messages to any channel
|
|
|
|
n_bytes = 500
|
|
|
|
bid = bidding.get_min_bid(n_bytes)
|
|
|
|
msg = rnd_string(n_bytes)
|
|
|
|
mock_new_invoice.return_value = (True,
|
|
|
|
new_invoice(1, InvoiceStatus.pending,
|
|
|
|
bid))
|
|
|
|
rv = client.post('/admin/order',
|
|
|
|
data={
|
|
|
|
'bid': bid,
|
|
|
|
'message': msg.encode(),
|
|
|
|
'channel': channel
|
|
|
|
})
|
|
|
|
assert rv.status_code == HTTPStatus.OK
|
|
|
|
check_upload(rv.get_json()['uuid'], msg)
|
|
|
|
|
|
|
|
|
2021-07-20 11:00:49 -03:00
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_confirm_tx_missing_or_invalid_param(mock_new_invoice, client):
|
|
|
|
post_rv = client.post('/order/tx/1')
|
|
|
|
assert post_rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
post_rv = client.post('/order/tx/1', data={'regions': 'a'})
|
|
|
|
assert post_rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
post_rv = client.post('/order/tx/1', data={'regions': 1})
|
|
|
|
assert post_rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
# Confirm tx of a non existing sequence number
|
|
|
|
post_rv = client.post('/order/tx/2', data={"regions": [[1]]})
|
|
|
|
assert post_rv.status_code == HTTPStatus.NOT_FOUND
|
|
|
|
assert_error(post_rv.get_json(), 'SEQUENCE_NUMBER_NOT_FOUND')
|
|
|
|
# Create a test order but confirm Tx for an invalid region
|
|
|
|
generate_test_order(mock_new_invoice, client, tx_seq_num=1)
|
|
|
|
post_rv = client.post('/order/tx/1', data={'regions': [[20, 1]]})
|
2021-12-21 16:32:10 -03:00
|
|
|
assert post_rv.status_code == HTTPStatus.BAD_REQUEST
|
2021-07-20 11:00:49 -03:00
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_confirm_tx(mock_new_invoice, client):
|
|
|
|
uuid = generate_test_order(mock_new_invoice, client, tx_seq_num=1)['uuid']
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
|
|
|
|
# Confirm tx for a single region
|
2021-12-21 16:32:10 -03:00
|
|
|
post_rv = client.post('/order/tx/1',
|
|
|
|
data={'regions': [[Regions.t11n_afr.value]]})
|
2023-02-16 15:48:29 -03:00
|
|
|
assert len(db_order.tx_confirmations) == 1
|
|
|
|
assert db_order.tx_confirmations[0].region_id == SATELLITE_REGIONS[
|
2021-12-21 16:32:10 -03:00
|
|
|
Regions.t11n_afr]['id']
|
2021-07-20 11:00:49 -03:00
|
|
|
|
|
|
|
# Confirm tx for multiple regions
|
2021-12-21 16:32:10 -03:00
|
|
|
post_rv = client.post(
|
|
|
|
'/order/tx/1',
|
|
|
|
data={
|
|
|
|
'regions':
|
|
|
|
[[Regions.g18.value, Regions.e113.value, Regions.t11n_afr.value]]
|
|
|
|
})
|
2021-07-20 11:00:49 -03:00
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
2023-02-16 15:48:29 -03:00
|
|
|
assert len(db_order.tx_confirmations) == 3
|
|
|
|
assert db_order.tx_confirmations[0].region_id == SATELLITE_REGIONS[
|
|
|
|
Regions.t11n_afr]['id']
|
|
|
|
assert db_order.tx_confirmations[1].region_id == SATELLITE_REGIONS[
|
2021-12-21 16:32:10 -03:00
|
|
|
Regions.g18]['id']
|
2023-02-16 15:48:29 -03:00
|
|
|
assert db_order.tx_confirmations[2].region_id == SATELLITE_REGIONS[
|
2021-12-21 16:32:10 -03:00
|
|
|
Regions.e113]['id']
|
2021-07-20 11:00:49 -03:00
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_confirm_rx_missing_or_invalid_param(mock_new_invoice, client):
|
|
|
|
post_rv = client.post('/order/rx/1')
|
|
|
|
assert post_rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
post_rv = client.post('/order/rx/1', data={'region': 'a'})
|
|
|
|
assert post_rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
post_rv = client.post('/order/rx/1', data={'regions': [[1, 2]]})
|
|
|
|
assert post_rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
# Confirm rx of a non existing sequence number
|
|
|
|
post_rv = client.post('/order/rx/2', data={"region": 1})
|
|
|
|
assert post_rv.status_code == HTTPStatus.NOT_FOUND
|
|
|
|
assert_error(post_rv.get_json(), 'SEQUENCE_NUMBER_NOT_FOUND')
|
|
|
|
# Create a test order but confirm Rx for an invalid region
|
|
|
|
generate_test_order(mock_new_invoice, client, tx_seq_num=1)
|
|
|
|
post_rv = client.post('/order/rx/1', data={'region': 20})
|
2021-12-21 16:32:10 -03:00
|
|
|
assert post_rv.status_code == HTTPStatus.BAD_REQUEST
|
2021-07-20 11:00:49 -03:00
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_confirm_rx(mock_new_invoice, client):
|
|
|
|
uuid = generate_test_order(mock_new_invoice, client, tx_seq_num=1)['uuid']
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
|
2021-12-21 16:32:10 -03:00
|
|
|
post_rv = client.post('/order/rx/1', data={'region': Regions.g18.value})
|
2021-07-20 11:00:49 -03:00
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
2023-02-16 15:48:29 -03:00
|
|
|
assert len(db_order.rx_confirmations) == 1
|
|
|
|
assert db_order.rx_confirmations[0].region_id == SATELLITE_REGIONS[
|
2021-12-21 16:32:10 -03:00
|
|
|
Regions.g18]['id']
|
2021-07-20 11:00:49 -03:00
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"tx_regions",
|
|
|
|
[[Regions.g18.value], [Regions.g18.value, Regions.e113.value],
|
|
|
|
[
|
|
|
|
Regions.g18.value, Regions.e113.value, Regions.t11n_afr.value,
|
|
|
|
Regions.t11n_eu.value, Regions.t18v_c.value
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Regions.g18.value, Regions.e113.value, Regions.t11n_afr.value,
|
|
|
|
Regions.t11n_eu.value, Regions.t18v_c.value, Regions.t18v_c.value
|
|
|
|
]])
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"rx_regions",
|
|
|
|
[[Regions.g18.value], [Regions.g18.value, Regions.e113.value],
|
|
|
|
[Regions.g18.value, Regions.e113.value, Regions.t18v_c.value],
|
|
|
|
[
|
|
|
|
Regions.g18.value, Regions.e113.value, Regions.t18v_c.value,
|
|
|
|
Regions.t18v_c.value
|
|
|
|
]])
|
2021-12-21 16:31:41 -03:00
|
|
|
def test_sent_or_received_criteria_met_inadequate_regions(
|
|
|
|
mock_new_invoice, client, tx_regions, rx_regions):
|
2021-07-20 11:00:49 -03:00
|
|
|
uuid = generate_test_order(mock_new_invoice,
|
|
|
|
client,
|
2021-12-21 16:32:30 -03:00
|
|
|
order_status=OrderStatus.transmitting,
|
2021-07-20 11:00:49 -03:00
|
|
|
tx_seq_num=1)['uuid']
|
|
|
|
|
|
|
|
# Confirm tx
|
|
|
|
post_rv = client.post('/order/tx/1', data={'regions': [tx_regions]})
|
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
|
|
|
|
|
|
|
# Confirm rx
|
|
|
|
for region in rx_regions:
|
|
|
|
post_rv = client.post('/order/rx/1', data={'region': region})
|
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
|
|
|
|
2021-12-21 16:32:30 -03:00
|
|
|
# The order status should not change to sent nor received because not
|
|
|
|
# enough regions have confirmed tx/rx
|
2021-07-20 11:00:49 -03:00
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
2021-12-21 16:32:30 -03:00
|
|
|
assert db_order.status == OrderStatus.confirming.value
|
2021-07-20 11:00:49 -03:00
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
2021-12-21 16:31:41 -03:00
|
|
|
def test_sent_or_received_criteria_met_for_unsent_order(
|
|
|
|
mock_new_invoice, client):
|
2021-07-20 11:00:49 -03:00
|
|
|
uuid = generate_test_order(mock_new_invoice, client, tx_seq_num=1)['uuid']
|
|
|
|
# Confirm tx for all 6 regions
|
|
|
|
post_rv = client.post('/order/tx/1',
|
|
|
|
data={'regions': [[e.value for e in Regions]]})
|
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
|
|
|
|
|
|
|
# Confirm rx for all regions except africa and europe
|
|
|
|
for region in [
|
|
|
|
Regions.g18.value, Regions.e113.value, Regions.t18v_c.value,
|
|
|
|
Regions.t18v_ku.value
|
|
|
|
]:
|
|
|
|
post_rv = client.post('/order/rx/1', data={'region': region})
|
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
|
|
|
|
|
|
|
# Although all the required Tx/Rx confirmations are available, the order
|
|
|
|
# still cannot change to "received" state, as that requires the order to be
|
|
|
|
# in "sent" state before. The test order is still in "pending" state.
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
assert db_order.status == OrderStatus.pending.value
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
2021-12-21 16:31:41 -03:00
|
|
|
def test_sent_or_received_criteria_met_successfully(mock_new_invoice, client):
|
2021-07-20 11:00:49 -03:00
|
|
|
uuid = generate_test_order(mock_new_invoice,
|
|
|
|
client,
|
2021-12-21 16:31:41 -03:00
|
|
|
order_status=OrderStatus.transmitting,
|
2021-07-20 11:00:49 -03:00
|
|
|
tx_seq_num=1)['uuid']
|
|
|
|
|
|
|
|
# Confirm tx for all 6 regions
|
|
|
|
post_rv = client.post('/order/tx/1',
|
|
|
|
data={'regions': [[e.value for e in Regions]]})
|
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
2021-12-21 16:31:41 -03:00
|
|
|
# Order's status should change to sent
|
2021-07-20 11:00:49 -03:00
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
assert db_order.status == OrderStatus.sent.value
|
|
|
|
|
|
|
|
# Confirm rx for all regions except africa and europe
|
|
|
|
for region in [
|
|
|
|
Regions.g18.value, Regions.e113.value, Regions.t18v_c.value,
|
|
|
|
Regions.t18v_ku.value
|
|
|
|
]:
|
|
|
|
post_rv = client.post('/order/rx/1', data={'region': region})
|
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
|
|
|
|
|
|
|
# Order's status should change to received
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
assert db_order.status == OrderStatus.received.value
|
|
|
|
|
|
|
|
# Synthesized rx_confirmations for africa and europe should be created
|
|
|
|
for region in [Regions.t11n_afr, Regions.t11n_eu]:
|
|
|
|
db_rx_confirmation = RxConfirmation.query.filter_by(
|
|
|
|
order_id=db_order.id).filter_by(
|
2021-12-21 16:32:10 -03:00
|
|
|
region_id=SATELLITE_REGIONS[region]['id']).all()
|
2021-07-20 11:00:49 -03:00
|
|
|
assert len(db_rx_confirmation) == 1
|
|
|
|
assert db_rx_confirmation[0].presumed
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_confirm_tx_repeated_regions(mock_new_invoice, client):
|
|
|
|
uuid = generate_test_order(mock_new_invoice, client, tx_seq_num=1)['uuid']
|
|
|
|
db_order1 = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
|
|
|
|
uuid = generate_test_order(mock_new_invoice, client, tx_seq_num=2)['uuid']
|
|
|
|
db_order2 = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
|
|
|
|
# Confirm tx for a single region
|
2021-12-21 16:32:10 -03:00
|
|
|
post_rv = client.post('/order/tx/1',
|
|
|
|
data={'regions': [[Regions.t11n_afr.value]]})
|
2021-07-20 11:00:49 -03:00
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
2023-02-16 15:48:29 -03:00
|
|
|
assert len(db_order1.tx_confirmations) == 1
|
2021-07-20 11:00:49 -03:00
|
|
|
|
|
|
|
# Re-Confirm tx for the same region
|
2021-12-21 16:32:10 -03:00
|
|
|
post_rv = client.post('/order/tx/1',
|
|
|
|
data={'regions': [[Regions.t11n_afr.value]]})
|
2021-07-20 11:00:49 -03:00
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
2023-02-16 15:48:29 -03:00
|
|
|
assert len(db_order1.tx_confirmations) == 1
|
2021-07-20 11:00:49 -03:00
|
|
|
|
|
|
|
# Confirm tx for multiple regions, including t11n_afr again
|
2021-12-21 16:32:10 -03:00
|
|
|
post_rv = client.post(
|
|
|
|
'/order/tx/1',
|
|
|
|
data={
|
|
|
|
'regions':
|
|
|
|
[[Regions.g18.value, Regions.t18v_c.value, Regions.t11n_afr.value]]
|
|
|
|
})
|
2021-07-20 11:00:49 -03:00
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
2023-02-16 15:48:29 -03:00
|
|
|
assert len(db_order1.tx_confirmations) == 3
|
2021-07-20 11:00:49 -03:00
|
|
|
|
|
|
|
# Confirm tx for multiple regions, different order_id
|
|
|
|
post_rv = client.post('/order/tx/2',
|
|
|
|
data={
|
|
|
|
'regions': [[
|
2021-12-21 16:32:10 -03:00
|
|
|
Regions.t11n_eu.value, Regions.t18v_ku.value,
|
|
|
|
Regions.t18v_ku.value
|
2021-07-20 11:00:49 -03:00
|
|
|
]]
|
|
|
|
})
|
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
2023-02-16 15:48:29 -03:00
|
|
|
assert len(db_order2.tx_confirmations) == 2
|
2021-07-20 11:00:49 -03:00
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_confirm_rx_repeated_regions(mock_new_invoice, client):
|
|
|
|
uuid = generate_test_order(mock_new_invoice, client, tx_seq_num=1)['uuid']
|
|
|
|
db_order1 = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
|
|
|
|
uuid = generate_test_order(mock_new_invoice, client, tx_seq_num=2)['uuid']
|
|
|
|
db_order2 = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
|
|
|
|
# Confirm rx for a region
|
2021-12-21 16:32:10 -03:00
|
|
|
post_rv = client.post('/order/rx/1', data={'region': Regions.g18.value})
|
2021-07-20 11:00:49 -03:00
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
2023-02-16 15:48:29 -03:00
|
|
|
assert len(db_order1.rx_confirmations) == 1
|
2021-07-20 11:00:49 -03:00
|
|
|
|
|
|
|
# Re-Confirm rx for the same region
|
2021-12-21 16:32:10 -03:00
|
|
|
post_rv = client.post('/order/rx/1', data={'region': Regions.g18.value})
|
2021-07-20 11:00:49 -03:00
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
2023-02-16 15:48:29 -03:00
|
|
|
assert len(db_order1.rx_confirmations) == 1
|
2021-07-20 11:00:49 -03:00
|
|
|
|
|
|
|
# Confirm rx for the same region, different order_id
|
2021-12-21 16:32:10 -03:00
|
|
|
post_rv = client.post('/order/rx/2', data={'region': Regions.g18.value})
|
2021-07-20 11:00:49 -03:00
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
2023-02-16 15:48:29 -03:00
|
|
|
assert len(db_order2.rx_confirmations) == 1
|
2021-07-20 11:00:49 -03:00
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
@pytest.mark.parametrize("status", [
|
|
|
|
OrderStatus.transmitting, OrderStatus.sent, OrderStatus.received,
|
|
|
|
OrderStatus.cancelled, OrderStatus.expired
|
|
|
|
])
|
|
|
|
def test_try_to_pay_a_non_pending_order(mock_new_invoice, client, status):
|
|
|
|
n_bytes = 500
|
|
|
|
invoice = new_invoice(1, InvoiceStatus.pending,
|
|
|
|
bidding.get_min_bid(n_bytes))
|
|
|
|
mock_new_invoice.return_value = (True, invoice)
|
|
|
|
post_rv = place_order(client, n_bytes)
|
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
|
|
|
uuid_order = post_rv.get_json()['uuid']
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid_order).first()
|
|
|
|
db_order.status = status.value
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
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
|
|
|
|
|
Support multiple parallel logical message channels
The same server now can handle multiple logical channels, on which the
transmitter logic runs independently. That is, while previously a single
message would be in transmitting state at a time, now multiple messages
can be in transmitting state as long as they belong to distinct logical
channels.
The supported channels each have different permissions. The user channel
is where users can post, get, and delete messages as needed. In
contrast, the other channels do not grant all permissions to users. Some
are read-only (users can get but not post) and there is a channel (the
auth channel) on which users have no permissions (neither get nor post).
For the channels on which users do not have all permissions (get, post,
and delete), this patch adds admin-specific routes, which are prefixed
by /admin/. The /admin/ route is protected via SSL in production and
allows the admin host to send GET/POST/DELETE requests normally. Hence,
for instance, the admin host can post a message on the auth channel
(with POST /admin/order) and read it (with GET /admin/order) for
transmission over satellite, whereas regulars cannot. With this scheme,
the auth channel messages are accessible exclusively over satellite (and
not over the internet).
The admin routes were added to the following endpoints:
- /order/<uuid> (GET and DELETE requests)
- /order (POST request)
- /orders/<state> (GET request)
- /message/<tx_seq_num> (GET request)
The messages posted by the admin are not paid, so this patch removes the
requirement of invoice generation and payment. Only paid orders now
generate an invoice. Thus, the POST request to the /order/ endpoint does
not return an invoice for non-paid (admin-only) messages.
Also, this patch updates the queue page to display the orders separately
for each channel. The query string channel parameter determines which
channel the page shows.
Finally, this patch updates the events published into the Redis db on
transmission. The event includes the corresponding logical channel so
that SSE events can be subscribed independently for each channel.
2023-01-31 18:14:55 -03:00
|
|
|
# Refetch the order and the invoice from the database.
|
|
|
|
# The expectation is that invoice changes its status to paid because
|
|
|
|
# it had the pending status, but order keeps its current status.
|
2021-07-20 11:00:49 -03:00
|
|
|
db_invoice = Invoice.query.filter_by(lid=invoice.lid).first()
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid_order).first()
|
|
|
|
assert db_invoice.status == InvoiceStatus.paid.value
|
|
|
|
assert db_order.status == status.value
|
|
|
|
assert db_invoice.paid_at is not None
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_bump_non_existing_order(mock_new_invoice, client):
|
|
|
|
bid_increase = 2500
|
|
|
|
mock_new_invoice.return_value = (True,
|
|
|
|
new_invoice(1, InvoiceStatus.pending,
|
|
|
|
bid_increase))
|
|
|
|
bump_rv = client.post('/order/12345/bump',
|
|
|
|
data={
|
|
|
|
'bid_increase': bid_increase,
|
|
|
|
},
|
|
|
|
headers={'X-Auth-Token': "non-existing-token"})
|
|
|
|
assert bump_rv.status_code == HTTPStatus.NOT_FOUND
|
|
|
|
assert_error(bump_rv.get_json(), 'ORDER_NOT_FOUND')
|
2021-12-21 16:32:10 -03:00
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_create_order_with_invalid_region(mock_new_invoice, client):
|
|
|
|
n_bytes = 500
|
|
|
|
bid = bidding.get_min_bid(n_bytes)
|
|
|
|
msg = rnd_string(n_bytes)
|
|
|
|
mock_new_invoice.return_value = (True,
|
|
|
|
new_invoice(1, InvoiceStatus.pending,
|
|
|
|
bid))
|
|
|
|
post_rv = client.post('/order',
|
|
|
|
data={
|
|
|
|
'bid': bid,
|
|
|
|
'message': msg.encode(),
|
|
|
|
'regions': 'a'
|
|
|
|
})
|
|
|
|
assert post_rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
post_rv = client.post('/order',
|
|
|
|
data={
|
|
|
|
'bid': bid,
|
|
|
|
'message': msg.encode(),
|
|
|
|
'regions': 1
|
|
|
|
})
|
|
|
|
assert post_rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
post_rv = client.post('/order',
|
|
|
|
data={
|
|
|
|
'bid': bid,
|
|
|
|
'message': msg.encode(),
|
|
|
|
'regions': [[6, 1]]
|
|
|
|
})
|
|
|
|
assert post_rv.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_sent_or_received_criteria_met_successfully_for_subset_of_regions(
|
|
|
|
mock_new_invoice, client):
|
|
|
|
selected_regions = [
|
|
|
|
Regions.g18.value, Regions.e113.value, Regions.t11n_afr.value
|
|
|
|
]
|
|
|
|
uuid = generate_test_order(mock_new_invoice,
|
|
|
|
client,
|
|
|
|
tx_seq_num=1,
|
|
|
|
order_status=OrderStatus.transmitting,
|
|
|
|
regions=selected_regions)['uuid']
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
|
|
|
|
# Only the selected regions are required to confirm Tx in order for the
|
|
|
|
# order to change into "sent" state.
|
|
|
|
post_rv = client.post('/order/tx/1', data={'regions': [selected_regions]})
|
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
2023-02-16 15:48:29 -03:00
|
|
|
assert len(db_order.tx_confirmations) == 3
|
|
|
|
assert db_order.tx_confirmations[0].region_id == SATELLITE_REGIONS[
|
2021-12-21 16:32:10 -03:00
|
|
|
Regions.g18]['id']
|
2023-02-16 15:48:29 -03:00
|
|
|
assert db_order.tx_confirmations[1].region_id == SATELLITE_REGIONS[
|
2021-12-21 16:32:10 -03:00
|
|
|
Regions.e113]['id']
|
2023-02-16 15:48:29 -03:00
|
|
|
assert db_order.tx_confirmations[2].region_id == SATELLITE_REGIONS[
|
2021-12-21 16:32:10 -03:00
|
|
|
Regions.t11n_afr]['id']
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
assert db_order.status == OrderStatus.sent.value
|
|
|
|
|
|
|
|
# Confirm rx only for the monitored regions in the order request (i.e.,
|
|
|
|
# excluding t11n_afr)
|
|
|
|
for region in [Regions.g18.value, Regions.e113.value]:
|
|
|
|
post_rv = client.post('/order/rx/1', data={'region': region})
|
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
|
|
|
|
|
|
|
# The order's status should change to received
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
assert db_order.status == OrderStatus.received.value
|
|
|
|
|
|
|
|
# A synthesized Rx confirmation should be created for t11n_afr
|
2023-02-16 15:48:29 -03:00
|
|
|
synth_rx_confirmations = RxConfirmation.query.filter_by(
|
2021-12-21 16:32:10 -03:00
|
|
|
order_id=db_order.id).filter_by(presumed=True).all()
|
2023-02-16 15:48:29 -03:00
|
|
|
assert len(synth_rx_confirmations) == 1
|
|
|
|
assert synth_rx_confirmations[0].region_id == SATELLITE_REGIONS[
|
2021-12-21 16:32:10 -03:00
|
|
|
Regions.t11n_afr]['id']
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_sent_or_received_criteria_met_for_subset_of_presumed_rx_regions(
|
|
|
|
mock_new_invoice, client):
|
|
|
|
selected_regions = [Regions.t11n_afr.value, Regions.t11n_eu.value]
|
|
|
|
uuid = generate_test_order(mock_new_invoice,
|
|
|
|
client,
|
|
|
|
tx_seq_num=1,
|
|
|
|
order_status=OrderStatus.transmitting,
|
|
|
|
regions=selected_regions)['uuid']
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
|
|
|
|
# Confirm tx only for the two selected regions
|
|
|
|
post_rv = client.post('/order/tx/1', data={'regions': [selected_regions]})
|
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
|
|
|
|
|
|
|
# The order status should change to received and NOT sent. The two regions
|
|
|
|
# in the order request were t11n_afr and t11n_eu. None of these regions
|
|
|
|
# send Rx confirmations. As a result, the order should automatically move
|
|
|
|
# from sent to received without any Rx confirmations.
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
assert db_order.status == OrderStatus.received.value
|
|
|
|
|
|
|
|
# The synthesized Rx confirmations for Africa and Europe should be created
|
|
|
|
db_rx_confirmation = RxConfirmation.query.filter_by(
|
|
|
|
order_id=db_order.id).filter_by(presumed=True).all()
|
|
|
|
assert len(db_rx_confirmation) == 2
|
|
|
|
assert db_rx_confirmation[0].region_id == SATELLITE_REGIONS[
|
|
|
|
Regions.t11n_afr]['id']
|
|
|
|
assert db_rx_confirmation[1].region_id == SATELLITE_REGIONS[
|
|
|
|
Regions.t11n_eu]['id']
|
|
|
|
|
|
|
|
|
|
|
|
@patch('orders.new_invoice')
|
|
|
|
def test_sent_or_received_criteria_met_invalid_tx_subset(
|
|
|
|
mock_new_invoice, client):
|
|
|
|
selected_regions = [Regions.g18.value, Regions.e113.value]
|
|
|
|
other_regions = [
|
|
|
|
Regions.t11n_afr.value, Regions.t11n_eu.value, Regions.t18v_c.value,
|
|
|
|
Regions.t18v_ku.value
|
|
|
|
]
|
|
|
|
uuid = generate_test_order(mock_new_invoice,
|
|
|
|
client,
|
|
|
|
tx_seq_num=1,
|
|
|
|
order_status=OrderStatus.transmitting,
|
|
|
|
regions=selected_regions)['uuid']
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
|
|
|
|
# Confirm tx over regions other than those explicitly requested
|
|
|
|
# by the order
|
|
|
|
post_rv = client.post('/order/tx/1', data={'regions': [other_regions]})
|
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
|
|
|
|
2021-12-21 16:32:30 -03:00
|
|
|
# The order status should change to "confirming"
|
2021-12-21 16:32:10 -03:00
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
2021-12-21 16:32:30 -03:00
|
|
|
assert db_order.status == OrderStatus.confirming.value
|
2021-12-21 16:32:10 -03:00
|
|
|
|
|
|
|
# Confirm tx
|
|
|
|
post_rv = client.post('/order/tx/1', data={'regions': [selected_regions]})
|
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
|
|
|
# Now the status should get updated to sent
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
assert db_order.status == OrderStatus.sent.value
|
|
|
|
|
|
|
|
# Confirm rx
|
|
|
|
for region in other_regions:
|
|
|
|
post_rv = client.post('/order/rx/1', data={'region': region})
|
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
|
|
|
# The status should stay as sent so long as rx is not confirmed
|
|
|
|
# by the two regions in the request (g18, e113)
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
assert db_order.status == OrderStatus.sent.value
|
|
|
|
|
|
|
|
for region in [Regions.g18.value, Regions.e113.value]:
|
|
|
|
post_rv = client.post('/order/rx/1', data={'region': region})
|
|
|
|
assert post_rv.status_code == HTTPStatus.OK
|
|
|
|
# Now the status should change to received
|
|
|
|
db_order = Order.query.filter_by(uuid=uuid).first()
|
|
|
|
assert db_order.status == OrderStatus.received.value
|