import pytest import random from datetime import timedelta from http import HTTPStatus from time import sleep from unittest.mock import patch from constants import InvoiceStatus, OrderStatus from database import db from models import Order from invoice_helpers import pay_invoice import bidding import constants import server from common import new_invoice, place_order, generate_test_order from error import assert_error @pytest.fixture def client(mockredis): app = server.create_app(from_test=True) app.app_context().push() with app.test_client() as client: yield client server.teardown_app(app) @pytest.mark.parametrize("state", ['pending', 'queued', 'sent']) def test_get_orders_invalid_before_parameter(client, state): get_rv = client.get(f'/orders/{state}?before=') assert get_rv.status_code == HTTPStatus.BAD_REQUEST get_rv = client.get(f'/orders/{state}?before=sometext') assert get_rv.status_code == HTTPStatus.BAD_REQUEST get_rv = client.get(f'/orders/{state}?before=2021-13-11') assert get_rv.status_code == HTTPStatus.BAD_REQUEST get_rv = client.get(f'/orders/{state}?before=2021.05.10') assert get_rv.status_code == HTTPStatus.BAD_REQUEST get_rv = client.get(f'/orders/{state}?before=2021-05-1a0T19:51:45') assert get_rv.status_code == HTTPStatus.BAD_REQUEST get_rv = client.get(f'/orders/{state}?before=2021.05.10T19:51:45') assert get_rv.status_code == HTTPStatus.BAD_REQUEST get_rv = client.get(f'/orders/{state}?before=2021-05-10T25:51:45') assert get_rv.status_code == HTTPStatus.BAD_REQUEST @pytest.mark.parametrize("state", ['pending', 'queued', 'sent']) def test_get_orders_invalid_limit(client, state): get_rv = client.get(f'/orders/{state}?limit=') assert get_rv.status_code == HTTPStatus.BAD_REQUEST get_rv = client.get(f'/orders/{state}?limit=sometext') assert get_rv.status_code == HTTPStatus.BAD_REQUEST get_rv = client.get(f'/orders/{state}?limit=1a2') assert get_rv.status_code == HTTPStatus.BAD_REQUEST get_rv = client.get(f'/orders/{state}?limit=-1') assert get_rv.status_code == HTTPStatus.BAD_REQUEST get_rv = client.get(f'/orders/{state}?limit=1.2') assert get_rv.status_code == HTTPStatus.BAD_REQUEST def test_try_to_get_invalid_order_state(client): for state in ['pendiing', 'someendpoint', 'Pending']: get_rv = client.get(f'/orders/{state}') assert get_rv.status_code == HTTPStatus.NOT_FOUND @patch('constants.PAGE_SIZE', 3) # change PAGE_SIZE to run this test faster @patch('orders.new_invoice') @pytest.mark.parametrize("state", ['pending', 'queued', 'sent']) def test_get_orders_before_parameter(mock_new_invoice, client, state): # Create PAGE_SIZE orders with the target state n_orders = constants.PAGE_SIZE n_bytes = 500 mock_new_invoice.return_value = (True, new_invoice(1, InvoiceStatus.pending, bidding.get_min_bid(n_bytes))) order_uuids = [] for i in range(n_orders): post_rv = place_order(client, n_bytes) assert post_rv.status_code == HTTPStatus.OK uuid = post_rv.get_json()['uuid'] order_uuids.append(uuid) db_order = Order.query.filter_by(uuid=uuid).first() db_order.status = OrderStatus.transmitting.value if \ state == 'queued' else OrderStatus[state].value db.session.commit() sleep(1.0) # to have different created_at # Fetch orders excluding the last last_db_order = Order.query.filter_by(uuid=order_uuids[-1]).first() last_created_at = last_db_order.created_at.isoformat() get_rv = client.get(f'/orders/{state}?before={last_created_at}') assert get_rv.status_code == HTTPStatus.OK fetched_uuids = [order['uuid'] for order in get_rv.get_json()] # The last order should be filtered out by the before filter expected_uuids = order_uuids[:-1] # Expected sorting: /orders/pending endpoint sorts by the created_at # timestamp, /orders/queued by the bid_per_byte ratio, and /orders/sent by # the transmission_at timestamp. if (state == 'pending'): expected_uuids.reverse() assert len(fetched_uuids) == n_orders - 1 assert fetched_uuids == expected_uuids @patch('orders.new_invoice') @pytest.mark.parametrize("state", ['pending', 'queued', 'sent']) def test_get_orders_limit_parameter(mock_new_invoice, client, state): n_bytes = 500 mock_new_invoice.return_value = (True, new_invoice(1, InvoiceStatus.pending, bidding.get_min_bid(n_bytes))) for i in range(constants.MAX_PAGE_SIZE + 1): post_rv = place_order(client, n_bytes) assert post_rv.status_code == HTTPStatus.OK uuid = post_rv.get_json()['uuid'] db_order = Order.query.filter_by(uuid=uuid).first() db_order.status = OrderStatus.transmitting.value if \ state == 'queued' else OrderStatus[state].value db.session.commit() # no limit parameter, max PAGE_SIZE should be returned get_rv = client.get(f'/orders/{state}') assert get_rv.status_code == HTTPStatus.OK get_json_resp = get_rv.get_json() assert len(get_json_resp) == constants.PAGE_SIZE # with limit parameter present get_rv = client.get(f'/orders/{state}?limit=10') assert get_rv.status_code == HTTPStatus.OK get_json_resp = get_rv.get_json() assert len(get_json_resp) == 10 # the limit should be within [1, MAX_PAGE_SIZE] get_rv = client.get(f'/orders/{state}?limit={constants.MAX_PAGE_SIZE}') assert get_rv.status_code == HTTPStatus.OK get_json_resp = get_rv.get_json() assert len(get_json_resp) == constants.MAX_PAGE_SIZE get_rv = client.get(f'/orders/{state}?limit={constants.MAX_PAGE_SIZE + 1}') assert get_rv.status_code == HTTPStatus.BAD_REQUEST get_rv = client.get(f'/orders/{state}?limit=0') assert get_rv.status_code == HTTPStatus.BAD_REQUEST @patch('orders.new_invoice') @pytest.mark.parametrize("state", ['pending', 'queued', 'sent']) @pytest.mark.parametrize("channel", constants.CHANNELS) def test_get_orders_channel_parameter(mock_new_invoice, client, state, channel): n_bytes = 500 mock_new_invoice.return_value = (True, new_invoice(1, InvoiceStatus.pending, bidding.get_min_bid(n_bytes))) # Place all orders as the admin for i in range(constants.MAX_PAGE_SIZE + 1): post_rv = place_order(client, n_bytes, channel=channel, admin=True) assert post_rv.status_code == HTTPStatus.OK uuid = post_rv.get_json()['uuid'] db_order = Order.query.filter_by(uuid=uuid).first() db_order.status = OrderStatus.transmitting.value if \ state == 'queued' else OrderStatus[state].value db.session.commit() # Get the orders of each channel as a regular user for _channel in constants.CHANNELS: get_rv = client.get(f'/orders/{state}?channel={_channel}') if 'get' in constants.CHANNEL_INFO[_channel].user_permissions: assert get_rv.status_code == HTTPStatus.OK get_json_resp = get_rv.get_json() n_expected_res = constants.PAGE_SIZE if _channel == channel else 0 assert len(get_json_resp) == n_expected_res else: assert get_rv.status_code == HTTPStatus.UNAUTHORIZED assert_error(get_rv.get_json(), 'ORDER_CHANNEL_UNAUTHORIZED_OP') # Get the orders of each channel as an admin user for _channel in constants.CHANNELS: get_rv = client.get(f'/admin/orders/{state}?channel={_channel}') assert get_rv.status_code == HTTPStatus.OK get_json_resp = get_rv.get_json() n_expected_res = constants.PAGE_SIZE if _channel == channel else 0 assert len(get_json_resp) == n_expected_res @patch('orders.new_invoice') def test_get_pending_orders(mock_new_invoice, client): # make some orders uuid_order1 = generate_test_order(mock_new_invoice, client)['uuid'] uuid_order2 = generate_test_order(mock_new_invoice, client)['uuid'] # manipulate order status for testing the GET /orders endpoint db_order = Order.query.filter_by(uuid=uuid_order2).first() pay_invoice(db_order.invoices[0]) db.session.commit() sleep(1.0) # to have different created_at uuid_order3 = generate_test_order(mock_new_invoice, client)['uuid'] get_rv = client.get('/orders/pending') assert get_rv.status_code == HTTPStatus.OK get_json_resp = get_rv.get_json() assert len(get_json_resp) == 2 # paid order should be filtered out assert get_json_resp[0]['uuid'] == uuid_order3 assert get_json_resp[1]['uuid'] == uuid_order1 @patch('orders.new_invoice') @patch('constants.PAGE_SIZE', 3) # change PAGE_SIZE to run this test faster def test_get_pending_orders_paging(mock_new_invoice, client): # make more orders than PAGE_SIZE n_orders = constants.PAGE_SIZE + 2 n_bytes = 500 mock_new_invoice.return_value = (True, new_invoice(1, InvoiceStatus.pending, bidding.get_min_bid(n_bytes))) order_uuids = [] for i in range(n_orders): post_rv = place_order(client, n_bytes) assert post_rv.status_code == HTTPStatus.OK order_uuids.append(post_rv.get_json()['uuid']) sleep(1.0) # to have different created_at # Check all orders were created all_orders = Order.query.filter_by(status=OrderStatus.pending.value).all() assert len(all_orders) == n_orders # Fetch the pending orders with paging get_rv = client.get('/orders/pending') assert get_rv.status_code == HTTPStatus.OK get_json_resp = get_rv.get_json() # Only the last PAGE_SIZE orders should be returned assert len(get_json_resp) == constants.PAGE_SIZE expected_uuids = order_uuids[-constants.PAGE_SIZE:] expected_uuids.reverse() # match the sorting from newest to oldest for i in range(constants.PAGE_SIZE): assert get_json_resp[i]['uuid'] == expected_uuids[i] @patch('orders.new_invoice') def test_get_queued_orders(mock_new_invoice, client): # Create some orders with different states order = {} for state in [ 'pending', 'paid', 'transmitting', 'sent', 'received', 'confirming' ]: order[state] = generate_test_order(mock_new_invoice, client, order_status=OrderStatus[state]) # Request queued orders get_rv = client.get('/orders/queued') assert get_rv.status_code == HTTPStatus.OK queued_uuids = [order['uuid'] for order in get_rv.get_json()] # The expectation is that only paid, transmitting and confirming # orders are returned assert len(queued_uuids) == 3 assert order['pending']['uuid'] not in queued_uuids assert order['paid']['uuid'] in queued_uuids assert order['transmitting']['uuid'] in queued_uuids assert order['sent']['uuid'] not in queued_uuids assert order['received']['uuid'] not in queued_uuids assert order['confirming']['uuid'] in queued_uuids @patch('orders.new_invoice') def test_get_queued_orders_sorting(mock_new_invoice, client): n_bytes = 500 mock_new_invoice.return_value = (True, new_invoice(1, InvoiceStatus.pending, bidding.get_min_bid(n_bytes))) n_orders = 10 bid_per_byte_map = {} for i in range(n_orders): post_rv = place_order(client, n_bytes) assert post_rv.status_code == HTTPStatus.OK order_uuid = post_rv.get_json()['uuid'] db_order = Order.query.filter_by(uuid=order_uuid).first() db_order.status = OrderStatus.transmitting.value bid_per_byte = random.randint(1, 10000) bid_per_byte_map[order_uuid] = bid_per_byte db_order.bid_per_byte = bid_per_byte db.session.commit() expected_sorted_orders = sorted(bid_per_byte_map.items(), key=lambda x: x[1], reverse=True) get_rv = client.get('/orders/queued') assert get_rv.status_code == HTTPStatus.OK get_json_resp = get_rv.get_json() assert len(get_json_resp) == n_orders for i in range(n_orders): assert get_json_resp[i]['uuid'] == expected_sorted_orders[i][0] assert get_json_resp[i]['bid_per_byte'] == expected_sorted_orders[i][1] @patch('orders.new_invoice') def test_get_sent_orders(mock_new_invoice, client): # Create some orders with different states order = {} for state in ['pending', 'transmitting', 'sent', 'received']: order[state] = generate_test_order(mock_new_invoice, client, order_status=OrderStatus[state]) # Request sent orders get_rv = client.get('/orders/sent') assert get_rv.status_code == HTTPStatus.OK get_json_resp = get_rv.get_json() sent_uuids = [order['uuid'] for order in get_rv.get_json()] # The expectation is that only sent and received orders are returned assert len(get_json_resp) == 2 # pending order should be filtered assert order['pending']['uuid'] not in sent_uuids assert order['transmitting']['uuid'] not in sent_uuids assert order['sent']['uuid'] in sent_uuids assert order['received']['uuid'] in sent_uuids @patch('orders.new_invoice') def test_get_sent_orders_sorting(mock_new_invoice, client): n_bytes = 500 mock_new_invoice.return_value = (True, new_invoice(1, InvoiceStatus.pending, bidding.get_min_bid(n_bytes))) order_uuids = [] for i in range(5): post_rv = place_order(client, n_bytes) assert post_rv.status_code == HTTPStatus.OK post_json_resp = post_rv.get_json() order_uuid = post_json_resp['uuid'] order_uuids.append(order_uuid) db_order = Order.query.filter_by(uuid=order_uuid).first() db_order.status = OrderStatus.sent.value order_created_at = db_order.created_at db_order.ended_transmission_at = order_created_at + timedelta(0, 100) db.session.commit() sleep(1.0) # to have different created_at order_uuids.reverse() # expected uuid order in the response get_rv = client.get('/orders/sent') assert get_rv.status_code == HTTPStatus.OK get_json_resp = get_rv.get_json() assert len(get_json_resp) == 5 for i in range(5): assert get_json_resp[i]['uuid'] == order_uuids[i] @patch('orders.new_invoice') @patch('constants.PAGE_SIZE', 3) # change PAGE_SIZE to run this test faster def test_get_sent_orders_paging(mock_new_invoice, client): # make more orders than PAGE_SIZE n_orders = constants.PAGE_SIZE + 2 n_bytes = 500 mock_new_invoice.return_value = (True, new_invoice(1, InvoiceStatus.pending, bidding.get_min_bid(n_bytes))) order_uuids = [] for i in range(n_orders): post_rv = place_order(client, n_bytes) assert post_rv.status_code == HTTPStatus.OK order_uuid = post_rv.get_json()['uuid'] order_uuids.append(order_uuid) db_order = Order.query.filter_by(uuid=order_uuid).first() db_order.status = OrderStatus.received.value order_created_at = db_order.created_at db_order.ended_transmission_at = order_created_at + timedelta(0, 100) db.session.commit() sleep(1.0) # to have different created_at # Check all orders were created all_orders = Order.query.filter_by(status=OrderStatus.received.value).all() assert len(all_orders) == n_orders # Fetch the sent orders with paging get_rv = client.get('/orders/sent') assert get_rv.status_code == HTTPStatus.OK get_json_resp = get_rv.get_json() # Only the last PAGE_SIZE orders should be returned assert len(get_json_resp) == constants.PAGE_SIZE expected_uuids = order_uuids[-constants.PAGE_SIZE:] expected_uuids.reverse() # match the sorting from newest to oldest for i in range(constants.PAGE_SIZE): assert get_json_resp[i]['uuid'] == expected_uuids[i] @patch('orders.new_invoice') @patch('constants.FORCE_PAYMENT', True) def test_create_order_with_force_payment_enabled(mock_new_invoice, client): uuid_order1 = generate_test_order(mock_new_invoice, client)['uuid'] uuid_order2 = generate_test_order(mock_new_invoice, client)['uuid'] db_order1 = Order.query.filter_by(uuid=uuid_order1).first() db_invoice1 = db_order1.invoices[0] db_order2 = Order.query.filter_by(uuid=uuid_order2).first() db_invoice2 = db_order2.invoices[0] # Since FORCE_PAYMENT is set and both orders have only one invoice, both # orders change their statuses to paid. Furthermore, the payment triggers a # Tx start verification and, since the transmit queue is empty, order1 # immediately changes to transmitting state. In contrast, order2 stays in # paid state as it needs to wait until the transmission of order1 finishes. assert db_order1.status == OrderStatus.transmitting.value assert db_invoice1.status == InvoiceStatus.paid.value assert db_invoice1.paid_at is not None assert db_order2.status == OrderStatus.paid.value assert db_invoice2.status == InvoiceStatus.paid.value assert db_invoice2.paid_at is not None