diff --git a/bigchaindb/web/views/transactions.py b/bigchaindb/web/views/transactions.py index e6cd87f1..63cd7bd2 100644 --- a/bigchaindb/web/views/transactions.py +++ b/bigchaindb/web/views/transactions.py @@ -3,15 +3,16 @@ For more information please refer to the documentation on ReadTheDocs: - https://bigchaindb.readthedocs.io/en/latest/drivers-clients/http-client-server-api.html """ - -import flask from flask import current_app, request, Blueprint +from flask_restful import Resource, Api import bigchaindb from bigchaindb import util from bigchaindb.web.views.base import make_error + transaction_views = Blueprint('transaction_views', __name__) +transaction_api = Api(transaction_views) # Unfortunately I cannot find a reference to this decorator. @@ -35,74 +36,83 @@ def record(state): 'performance.') -@transaction_views.route('/transactions/') -def get_transaction(tx_id): - """API endpoint to get details about a transaction. +class TransactionApi(Resource): + def get(self, tx_id): + """API endpoint to get details about a transaction. - Args: - tx_id (str): the id of the transaction. + Args: + tx_id (str): the id of the transaction. - Return: - A JSON string containing the data about the transaction. - """ + Return: + A JSON string containing the data about the transaction. + """ + pool = current_app.config['bigchain_pool'] - pool = current_app.config['bigchain_pool'] + with pool() as bigchain: + tx = bigchain.get_transaction(tx_id) - with pool() as bigchain: - tx = bigchain.get_transaction(tx_id) + if not tx: + return make_error(404) - if not tx: - return make_error(404) - - return flask.jsonify(**tx) + return tx -@transaction_views.route('/transactions/', methods=['POST']) -def create_transaction(): - """API endpoint to push transactions to the Federation. +class TransactionStatusApi(Resource): + def get(self, tx_id): + """API endpoint to get details about the status of a transaction. - Return: - A JSON string containing the data about the transaction. - """ - pool = current_app.config['bigchain_pool'] - monitor = current_app.config['monitor'] + Args: + tx_id (str): the id of the transaction. - # `force` will try to format the body of the POST request even if the `content-type` header is not - # set to `application/json` - tx = request.get_json(force=True) + Return: + A JSON string containing the status of the transaction. + Possible values: "valid", "invalid", "undecided", "backlog" + """ - with pool() as bigchain: - if tx['transaction']['operation'] == 'CREATE': - tx = util.transform_create(tx) - tx = bigchain.consensus.sign_transaction(tx, private_key=bigchain.me_private) + pool = current_app.config['bigchain_pool'] - if not bigchain.is_valid_transaction(tx): - return make_error(400, 'Invalid transaction') + with pool() as bigchain: + status = bigchain.get_status(tx_id) - with monitor.timer('write_transaction', rate=bigchaindb.config['statsd']['rate']): - bigchain.write_transaction(tx) + if not status: + return make_error(404) - return flask.jsonify(**tx) + return {'status': status} -@transaction_views.route('/transactions//status') -def get_transaction_status(tx_id): - """API endpoint to get details about the status of a transaction. +class TransactionListApi(Resource): + def post(self): + """API endpoint to push transactions to the Federation. - Args: - tx_id (str): the id of the transaction. + Return: + A JSON string containing the data about the transaction. + """ + pool = current_app.config['bigchain_pool'] + monitor = current_app.config['monitor'] - Return: - A JSON string containing the status of the transaction. - Possible values: "valid", "invalid", "undecided", "backlog", None - """ + # `force` will try to format the body of the POST request even if the `content-type` header is not + # set to `application/json` + tx = request.get_json(force=True) - pool = current_app.config['bigchain_pool'] + with pool() as bigchain: + if tx['transaction']['operation'] == 'CREATE': + tx = util.transform_create(tx) + tx = bigchain.consensus.sign_transaction(tx, private_key=bigchain.me_private) - with pool() as bigchain: - status = bigchain.get_status(tx_id) + if not bigchain.is_valid_transaction(tx): + return make_error(400, 'Invalid transaction') - if not status: - return make_error(404) + with monitor.timer('write_transaction', rate=bigchaindb.config['statsd']['rate']): + bigchain.write_transaction(tx) - return flask.jsonify({'status': status}) + return tx + +transaction_api.add_resource(TransactionApi, + '/transactions/', + strict_slashes=False) +transaction_api.add_resource(TransactionStatusApi, + '/transactions//status', + strict_slashes=False) +transaction_api.add_resource(TransactionListApi, + '/transactions', + strict_slashes=False) diff --git a/setup.py b/setup.py index 1a81def1..9360733c 100644 --- a/setup.py +++ b/setup.py @@ -102,6 +102,7 @@ setup( 'logstats==0.2.1', 'base58==0.2.2', 'flask==0.10.1', + 'flask-restful==0.3.5', 'requests~=2.9', 'gunicorn~=19.0', 'multipipes~=0.1.0', diff --git a/tests/web/test_basic_views.py b/tests/web/test_basic_views.py index 839c65d6..c58a8716 100644 --- a/tests/web/test_basic_views.py +++ b/tests/web/test_basic_views.py @@ -13,8 +13,12 @@ def test_get_transaction_endpoint(b, client, user_vk): input_tx = b.get_owned_ids(user_vk).pop() tx = b.get_transaction(input_tx['txid']) res = client.get(TX_ENDPOINT + input_tx['txid']) - assert tx == res.json assert res.status_code == 200 + assert tx == res.json + + res = client.get(TX_ENDPOINT + input_tx['txid'] + '/') + assert res.status_code == 200 + assert tx == res.json @pytest.mark.usefixtures('inputs') @@ -22,6 +26,9 @@ def test_get_transaction_returns_404_if_not_found(client): res = client.get(TX_ENDPOINT + '123') assert res.status_code == 404 + res = client.get(TX_ENDPOINT + '123/') + assert res.status_code == 404 + def test_api_endpoint_shows_basic_info(client): from bigchaindb import version @@ -40,6 +47,16 @@ def test_post_create_transaction_endpoint(b, client): assert res.json['transaction']['conditions'][0]['owners_after'][0] == vk +def test_post_create_transaction_endpoint_without_trailing_slash(b, client): + sk, vk = crypto.generate_key_pair() + + tx = util.create_and_sign_tx(sk, vk, vk, None, 'CREATE') + + res = client.post(TX_ENDPOINT[:-1], data=json.dumps(tx)) + assert res.json['transaction']['fulfillments'][0]['owners_before'][0] == b.me + assert res.json['transaction']['conditions'][0]['owners_after'][0] == vk + + @pytest.mark.usefixtures('inputs') def test_post_transfer_transaction_endpoint(b, client, user_vk, user_sk): sk, vk = crypto.generate_key_pair() @@ -71,9 +88,16 @@ def test_get_transaction_status_endpoint(b, client, user_vk): 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.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 +