diff --git a/bigchaindb/web/routes.py b/bigchaindb/web/routes.py index d2b5649a..75f2ffac 100644 --- a/bigchaindb/web/routes.py +++ b/bigchaindb/web/routes.py @@ -1,6 +1,11 @@ """ API routes definition """ from flask_restful import Api -from bigchaindb.web.views import info, transactions as tx, unspents +from bigchaindb.web.views import ( + info, + statuses, + transactions as tx, + unspents, +) def add_routes(app): @@ -17,8 +22,8 @@ def r(*args, **kwargs): ROUTES_API_V1 = [ + r('statuses/', statuses.StatusApi), r('transactions/', tx.TransactionApi), - r('transactions//status', tx.TransactionStatusApi), r('transactions', tx.TransactionListApi), r('unspents/', unspents.UnspentListApi), ] diff --git a/bigchaindb/web/views/statuses.py b/bigchaindb/web/views/statuses.py new file mode 100644 index 00000000..06b768e6 --- /dev/null +++ b/bigchaindb/web/views/statuses.py @@ -0,0 +1,62 @@ +"""This module provides the blueprint for the statuses API endpoints. + +For more information please refer to the documentation on ReadTheDocs: + - https://docs.bigchaindb.com/projects/server/en/latest/drivers-clients/ + http-client-server-api.html +""" +from flask import current_app +from flask_restful import Resource, reqparse + +from bigchaindb.web.views.base import make_error + + +class StatusApi(Resource): + def get(self): + """API endpoint to get details about the status of a transaction or a block. + + Return: + A ``dict`` in the format ``{'status': }``, where + ```` is one of "valid", "invalid", "undecided", "backlog". + """ + parser = reqparse.RequestParser() + parser.add_argument('tx_id', type=str) + parser.add_argument('block_id', type=str) + + args = parser.parse_args(strict=True) + tx_id = args['tx_id'] + block_id = args['block_id'] + + # logical xor - exactly one query argument required + if bool(tx_id) == bool(block_id): + return make_error(400, "Provide exactly one query parameter. Choices are: block_id, tx_id") + + pool = current_app.config['bigchain_pool'] + status, links = None, None + + with pool() as bigchain: + if tx_id: + status = bigchain.get_status(tx_id) + links = { + "tx": "/transactions/{}".format(tx_id) + } + + elif block_id: + _, status = bigchain.get_block(block_id=block_id, include_status=True) + # TODO: enable once blocks endpoint is available + # links = { + # "block": "/blocks/{}".format(args['block_id']) + # } + + if not status: + return make_error(404) + + response = { + 'status': status + } + + if links: + response.update({ + "_links": links + }) + + return response diff --git a/bigchaindb/web/views/transactions.py b/bigchaindb/web/views/transactions.py index 140ffef0..1b2495fb 100644 --- a/bigchaindb/web/views/transactions.py +++ b/bigchaindb/web/views/transactions.py @@ -46,29 +46,6 @@ class TransactionApi(Resource): return tx.to_dict() -class TransactionStatusApi(Resource): - def get(self, tx_id): - """API endpoint to get details about the status of a transaction. - - Args: - tx_id (str): the id of the transaction. - - Return: - A ``dict`` in the format ``{'status': }``, where - ```` is one of "valid", "invalid", "undecided", "backlog". - """ - - pool = current_app.config['bigchain_pool'] - - with pool() as bigchain: - status = bigchain.get_status(tx_id) - - if not status: - return make_error(404) - - return {'status': status} - - class TransactionListApi(Resource): def post(self): """API endpoint to push transactions to the Federation. diff --git a/tests/web/test_statuses.py b/tests/web/test_statuses.py new file mode 100644 index 00000000..4c65bec4 --- /dev/null +++ b/tests/web/test_statuses.py @@ -0,0 +1,98 @@ +import pytest + +from bigchaindb.models import Transaction + +STATUSES_ENDPOINT = '/api/v1/statuses' + + +@pytest.mark.bdb +@pytest.mark.usefixtures('inputs') +def test_get_transaction_status_endpoint(b, client, user_pk): + input_tx = b.get_owned_ids(user_pk).pop() + tx, status = b.get_transaction(input_tx.txid, include_status=True) + res = client.get(STATUSES_ENDPOINT + "?tx_id=" + input_tx.txid) + assert status == res.json['status'] + assert res.json['_links']['tx'] == "/transactions/{}".format(input_tx.txid) + assert res.status_code == 200 + + +@pytest.mark.bdb +def test_get_transaction_status_endpoint_returns_404_if_not_found(client): + res = client.get(STATUSES_ENDPOINT + "?tx_id=123") + assert res.status_code == 404 + + +@pytest.mark.bdb +def test_get_block_status_endpoint_undecided(b, client): + tx = Transaction.create([b.me], [([b.me], 1)]) + tx = tx.sign([b.me_private]) + + block = b.create_block([tx]) + b.write_block(block) + + status = b.block_election_status(block.id, block.voters) + + res = client.get(STATUSES_ENDPOINT + "?block_id=" + block.id) + assert status == res.json['status'] + assert '_links' not in res.json + assert res.status_code == 200 + + +@pytest.mark.bdb +@pytest.mark.usefixtures('inputs') +def test_get_block_status_endpoint_valid(b, client): + tx = Transaction.create([b.me], [([b.me], 1)]) + tx = tx.sign([b.me_private]) + + block = b.create_block([tx]) + b.write_block(block) + + # vote the block valid + vote = b.vote(block.id, b.get_last_voted_block().id, True) + b.write_vote(vote) + + status = b.block_election_status(block.id, block.voters) + + res = client.get(STATUSES_ENDPOINT + "?block_id=" + block.id) + assert status == res.json['status'] + assert '_links' not in res.json + assert res.status_code == 200 + + +@pytest.mark.bdb +@pytest.mark.usefixtures('inputs') +def test_get_block_status_endpoint_invalid(b, client): + tx = Transaction.create([b.me], [([b.me], 1)]) + tx = tx.sign([b.me_private]) + + block = b.create_block([tx]) + b.write_block(block) + + # vote the block valid + vote = b.vote(block.id, b.get_last_voted_block().id, False) + b.write_vote(vote) + + status = b.block_election_status(block.id, block.voters) + + res = client.get(STATUSES_ENDPOINT + "?block_id=" + block.id) + assert status == res.json['status'] + assert '_links' not in res.json + assert res.status_code == 200 + + +@pytest.mark.bdb +def test_get_block_status_endpoint_returns_404_if_not_found(client): + res = client.get(STATUSES_ENDPOINT + "?block_id=123") + assert res.status_code == 404 + + +@pytest.mark.bdb +def test_get_status_endpoint_returns_400_bad_query_params(client): + res = client.get(STATUSES_ENDPOINT) + assert res.status_code == 400 + + res = client.get(STATUSES_ENDPOINT + "?ts_id=123") + assert res.status_code == 400 + + res = client.get(STATUSES_ENDPOINT + "?tx_id=123&block_id=123") + assert res.status_code == 400 diff --git a/tests/web/test_transactions.py b/tests/web/test_transactions.py index fc1d02ac..ebb440d6 100644 --- a/tests/web/test_transactions.py +++ b/tests/web/test_transactions.py @@ -158,27 +158,3 @@ def test_post_invalid_transfer_transaction_returns_400(b, client, user_pk, user_ res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict())) assert res.status_code == 400 - - -@pytest.mark.bdb -@pytest.mark.usefixtures('inputs') -def test_get_transaction_status_endpoint(b, client, user_pk): - input_tx = b.get_owned_ids(user_pk).pop() - tx, status = b.get_transaction(input_tx.txid, include_status=True) - res = client.get(TX_ENDPOINT + input_tx.txid + "/status") - assert status == res.json['status'] - assert res.status_code == 200 - - res = client.get(TX_ENDPOINT + input_tx.txid + "/status/") - assert status == res.json['status'] - assert res.status_code == 200 - - -@pytest.mark.bdb -@pytest.mark.usefixtures('inputs') -def test_get_transaction_status_returns_404_if_not_found(client): - res = client.get(TX_ENDPOINT + '123' + "/status") - assert res.status_code == 404 - - res = client.get(TX_ENDPOINT + '123' + "/status/") - assert res.status_code == 404