From 7124adcd34aa09f3ae54def5e1452343f2469e1b Mon Sep 17 00:00:00 2001 From: diminator Date: Fri, 6 Jan 2017 15:55:07 +0100 Subject: [PATCH 1/6] implement statuses endpoint --- bigchaindb/web/routes.py | 8 ++- bigchaindb/web/views/statuses.py | 45 +++++++++++++ bigchaindb/web/views/transactions.py | 23 ------- tests/web/test_statuses.py | 94 ++++++++++++++++++++++++++++ tests/web/test_transactions.py | 23 ------- 5 files changed, 145 insertions(+), 48 deletions(-) create mode 100644 bigchaindb/web/views/statuses.py create mode 100644 tests/web/test_statuses.py diff --git a/bigchaindb/web/routes.py b/bigchaindb/web/routes.py index d2b5649a..dca7a518 100644 --- a/bigchaindb/web/routes.py +++ b/bigchaindb/web/routes.py @@ -1,6 +1,10 @@ """ 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 +21,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..11105ee1 --- /dev/null +++ b/bigchaindb/web/views/statuses.py @@ -0,0 +1,45 @@ +"""This module provides the blueprint for some basic 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) + + if sum(arg is not None for arg in args.values()) != 1: + return make_error(400, "Provide exactly one query parameter. Choices are: block_id, tx_id") + + pool = current_app.config['bigchain_pool'] + status = None + + with pool() as bigchain: + if args['tx_id']: + status = bigchain.get_status(args['tx_id']) + elif args['block_id']: + block = bigchain.get_block(block_id=args['block_id']) + if not block: + return make_error(404) + status = bigchain.block_election_status(block['id'], block['block']['voters']) + + if not status: + return make_error(404) + + return {'status': status} 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..a380db92 --- /dev/null +++ b/tests/web/test_statuses.py @@ -0,0 +1,94 @@ +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.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 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 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 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..b744dd32 100644 --- a/tests/web/test_transactions.py +++ b/tests/web/test_transactions.py @@ -159,26 +159,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 From 8fa231c040376690ce31e8675c8437ec8b3e41f1 Mon Sep 17 00:00:00 2001 From: diminator Date: Mon, 9 Jan 2017 17:14:22 +0100 Subject: [PATCH 2/6] include _links in transaction status payload --- bigchaindb/web/views/statuses.py | 23 ++++++++++++++++++++--- tests/web/test_statuses.py | 4 ++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/bigchaindb/web/views/statuses.py b/bigchaindb/web/views/statuses.py index 11105ee1..e4554565 100644 --- a/bigchaindb/web/views/statuses.py +++ b/bigchaindb/web/views/statuses.py @@ -1,4 +1,4 @@ -"""This module provides the blueprint for some basic API endpoints. +"""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/ @@ -28,18 +28,35 @@ class StatusApi(Resource): return make_error(400, "Provide exactly one query parameter. Choices are: block_id, tx_id") pool = current_app.config['bigchain_pool'] - status = None + status, links = None, None with pool() as bigchain: if args['tx_id']: status = bigchain.get_status(args['tx_id']) + links = { + "_links": { + "tx": "/transactions/{}".format(args['tx_id']) + } + } + elif args['block_id']: block = bigchain.get_block(block_id=args['block_id']) if not block: return make_error(404) status = bigchain.block_election_status(block['id'], block['block']['voters']) + # TODO: enable once blocks endpoint is available + # links = { + # "block": "/blocks/{}".format(args['block_id']) + # } if not status: return make_error(404) - return {'status': status} + response = { + 'status': status + } + + if links: + response.update(links) + + return response diff --git a/tests/web/test_statuses.py b/tests/web/test_statuses.py index a380db92..4c65bec4 100644 --- a/tests/web/test_statuses.py +++ b/tests/web/test_statuses.py @@ -12,6 +12,7 @@ def test_get_transaction_status_endpoint(b, client, user_pk): 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 @@ -33,6 +34,7 @@ def test_get_block_status_endpoint_undecided(b, client): 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 @@ -53,6 +55,7 @@ def test_get_block_status_endpoint_valid(b, client): 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 @@ -73,6 +76,7 @@ def test_get_block_status_endpoint_invalid(b, client): 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 From 15b91fea99ac0a3a49483663cfb19f999312d7a4 Mon Sep 17 00:00:00 2001 From: diminator Date: Mon, 9 Jan 2017 17:37:01 +0100 Subject: [PATCH 3/6] clean up links wrapper --- bigchaindb/web/views/statuses.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bigchaindb/web/views/statuses.py b/bigchaindb/web/views/statuses.py index e4554565..8ded2f29 100644 --- a/bigchaindb/web/views/statuses.py +++ b/bigchaindb/web/views/statuses.py @@ -34,9 +34,7 @@ class StatusApi(Resource): if args['tx_id']: status = bigchain.get_status(args['tx_id']) links = { - "_links": { - "tx": "/transactions/{}".format(args['tx_id']) - } + "tx": "/transactions/{}".format(args['tx_id']) } elif args['block_id']: @@ -57,6 +55,8 @@ class StatusApi(Resource): } if links: - response.update(links) + response.update({ + "_links": links + }) return response From 322d6bde129e28e846e3af4b7824952989670362 Mon Sep 17 00:00:00 2001 From: diminator Date: Tue, 10 Jan 2017 14:45:29 +0100 Subject: [PATCH 4/6] (fix): each import on its own line --- bigchaindb/web/routes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bigchaindb/web/routes.py b/bigchaindb/web/routes.py index dca7a518..75f2ffac 100644 --- a/bigchaindb/web/routes.py +++ b/bigchaindb/web/routes.py @@ -3,7 +3,8 @@ from flask_restful import Api from bigchaindb.web.views import ( info, statuses, - transactions as tx, unspents, + transactions as tx, + unspents, ) From ceeba0e89a25c87ef27f40b7aa6bc63dfa712572 Mon Sep 17 00:00:00 2001 From: diminator Date: Wed, 11 Jan 2017 13:45:47 +0100 Subject: [PATCH 5/6] logical xor & status flag --- bigchaindb/web/views/statuses.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/bigchaindb/web/views/statuses.py b/bigchaindb/web/views/statuses.py index 8ded2f29..06b768e6 100644 --- a/bigchaindb/web/views/statuses.py +++ b/bigchaindb/web/views/statuses.py @@ -23,25 +23,25 @@ class StatusApi(Resource): parser.add_argument('block_id', type=str) args = parser.parse_args(strict=True) + tx_id = args['tx_id'] + block_id = args['block_id'] - if sum(arg is not None for arg in args.values()) != 1: + # 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 args['tx_id']: - status = bigchain.get_status(args['tx_id']) + if tx_id: + status = bigchain.get_status(tx_id) links = { - "tx": "/transactions/{}".format(args['tx_id']) + "tx": "/transactions/{}".format(tx_id) } - elif args['block_id']: - block = bigchain.get_block(block_id=args['block_id']) - if not block: - return make_error(404) - status = bigchain.block_election_status(block['id'], block['block']['voters']) + 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']) From 8aa37277c2c4134107b9b4726b3dcec1881d1659 Mon Sep 17 00:00:00 2001 From: diminator Date: Wed, 11 Jan 2017 17:17:20 +0100 Subject: [PATCH 6/6] remove blank line for travis --- tests/web/test_transactions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/web/test_transactions.py b/tests/web/test_transactions.py index b744dd32..ebb440d6 100644 --- a/tests/web/test_transactions.py +++ b/tests/web/test_transactions.py @@ -158,4 +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 -