From d899d4c8b6e9f2cd9ed14476012fe17c1633e800 Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Tue, 17 Jan 2017 15:56:34 +0100 Subject: [PATCH 1/9] query string parameters test --- bigchaindb/web/views/parameters.py | 30 +++++++++++ bigchaindb/web/views/transactions.py | 15 +++++- tests/web/test_parameters.py | 79 ++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 bigchaindb/web/views/parameters.py create mode 100644 tests/web/test_parameters.py diff --git a/bigchaindb/web/views/parameters.py b/bigchaindb/web/views/parameters.py new file mode 100644 index 00000000..9968a659 --- /dev/null +++ b/bigchaindb/web/views/parameters.py @@ -0,0 +1,30 @@ +import re + + +def valid_txid(txid): + if re.match('^[a-fA-F0-9]{64}$', txid): + return txid.lower() + raise ValueError("Invalid hash") + + +def valid_bool(val): + if val == 'true': + return True + if val == 'false': + return False + raise ValueError('Boolean value must be "true" or "false" (lowercase)') + + +def valid_ed25519(key): + if (re.match('^[1-9a-zA-Z]{43,44}$', key) and not + re.match('.*[Il0O]', key)): + return key + raise ValueError("Invalid base58 ed25519 key") + + +def valid_operation(op): + if op == 'CREATE': + return 'CREATE' + if op == 'TRANSFER': + return 'TRANSFER' + raise ValueError('Operation must be "CREATE" or "TRANSFER') diff --git a/bigchaindb/web/views/transactions.py b/bigchaindb/web/views/transactions.py index fa544f65..e61a791f 100644 --- a/bigchaindb/web/views/transactions.py +++ b/bigchaindb/web/views/transactions.py @@ -5,9 +5,11 @@ For more information please refer to the documentation on ReadTheDocs: http-client-server-api.html """ import logging +import re from flask import current_app, request -from flask_restful import Resource +from flask_restful import Resource, reqparse + from bigchaindb.common.exceptions import ( AmountError, @@ -25,6 +27,7 @@ from bigchaindb.common.exceptions import ( import bigchaindb from bigchaindb.models import Transaction from bigchaindb.web.views.base import make_error +from bigchaindb.web.views import parameters logger = logging.getLogger(__name__) @@ -51,6 +54,16 @@ class TransactionApi(Resource): class TransactionListApi(Resource): + def get(self): + parser = reqparse.RequestParser() + parser.add_argument('operation', type=parameters.valid_operation) + parser.add_argument('unspent', type=parameters.valid_bool) + parser.add_argument('public_key', type=parameters.valid_ed25519, + action="append") + parser.add_argument('asset_id', type=parameters.valid_txid) + args = parser.parse_args() + return args + def post(self): """API endpoint to push transactions to the Federation. diff --git a/tests/web/test_parameters.py b/tests/web/test_parameters.py new file mode 100644 index 00000000..4044a273 --- /dev/null +++ b/tests/web/test_parameters.py @@ -0,0 +1,79 @@ +import pytest + + +def test_valid_txid(): + from bigchaindb.web.views.parameters import valid_txid + + valid = ['18ac3e7343f016890c510e93f935261169d9e3f565436429830faf0934f4f8e4', + '18AC3E7343F016890C510E93F935261169D9E3F565436429830FAF0934F4F8E4'] + for h in valid: + assert valid_txid(h) == h.lower() + + non = ['18ac3e7343f016890c510e93f935261169d9e3f565436429830faf0934f4f8e', + '18ac3e7343f016890c510e93f935261169d9e3f565436429830faf0934f4f8e45', + '18ac3e7343f016890c510e93f935261169d9e3f565436429830faf0934f4f8eg', + '18ac3e7343f016890c510e93f935261169d9e3f565436429830faf0934f4f8e ', + ''] + for h in non: + with pytest.raises(ValueError): + valid_txid(h) + + +def test_valid_bool(): + from bigchaindb.web.views.parameters import valid_bool + + assert valid_bool('true') == True + valid_bool('false') == False + + with pytest.raises(ValueError): + valid_bool('TRUE') + with pytest.raises(ValueError): + valid_bool('FALSE') + with pytest.raises(ValueError): + valid_bool('0') + with pytest.raises(ValueError): + valid_bool('1') + with pytest.raises(ValueError): + valid_bool('yes') + with pytest.raises(ValueError): + valid_bool('no') + + +def test_valid_ed25519(): + from bigchaindb.web.views.parameters import valid_ed25519 + + valid = ['123456789abcdefghijkmnopqrstuvwxyz1111111111', + '123456789ABCDEFGHJKLMNPQRSTUVWXYZ1111111111'] + for h in valid: + assert valid_ed25519(h) == h + + with pytest.raises(ValueError): + valid_ed25519('1234556789abcdefghijkmnopqrstuvwxyz1111111') + with pytest.raises(ValueError): + valid_ed25519('1234556789abcdefghijkmnopqrstuvwxyz1111111111') + with pytest.raises(ValueError): + valid_ed25519('123456789abcdefghijkmnopqrstuvwxyz111111111l') + with pytest.raises(ValueError): + valid_ed25519('123456789abcdefghijkmnopqrstuvwxyz111111111I') + with pytest.raises(ValueError): + valid_ed25519('1234556789abcdefghijkmnopqrstuvwxyz11111111O') + with pytest.raises(ValueError): + valid_ed25519('1234556789abcdefghijkmnopqrstuvwxyz111111110') + + +def test_valid_operation(): + from bigchaindb.web.views.parameters import valid_operation + + assert valid_operation('CREATE') == 'CREATE' + assert valid_operation('TRANSFER') == 'TRANSFER' + + with pytest.raises(ValueError): + valid_operation('create') + with pytest.raises(ValueError): + valid_operation('transfer') + with pytest.raises(ValueError): + valid_operation('GENESIS') + with pytest.raises(ValueError): + valid_operation('blah') + with pytest.raises(ValueError): + valid_operation('') From dbb3414fd0f73bcde71efb983fdb2a411dfbe47f Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Tue, 17 Jan 2017 18:02:09 +0100 Subject: [PATCH 2/9] generalise get_txids_by_asset_id into get_txids_filtered and remove get_transactions_by_asset_id --- bigchaindb/backend/mongodb/query.py | 31 +++++----- bigchaindb/backend/query.py | 13 ++++ bigchaindb/backend/rethinkdb/query.py | 33 +++++----- bigchaindb/core.py | 24 -------- tests/assets/test_digital_assets.py | 89 --------------------------- tests/backend/mongodb/test_queries.py | 45 ++++++++------ tests/backend/test_generics.py | 2 +- 7 files changed, 74 insertions(+), 163 deletions(-) diff --git a/bigchaindb/backend/mongodb/query.py b/bigchaindb/backend/mongodb/query.py index a9e52c82..c4e3cdc8 100644 --- a/bigchaindb/backend/mongodb/query.py +++ b/bigchaindb/backend/mongodb/query.py @@ -82,21 +82,6 @@ def get_blocks_status_from_transaction(conn, transaction_id): projection=['id', 'block.voters']) -@register_query(MongoDBConnection) -def get_txids_by_asset_id(conn, asset_id): - cursor = conn.db['bigchain'].aggregate([ - {'$match': { - 'block.transactions.asset.id': asset_id - }}, - {'$unwind': '$block.transactions'}, - {'$match': { - 'block.transactions.asset.id': asset_id - }}, - {'$project': {'block.transactions.id': True}} - ]) - return (elem['block']['transactions']['id'] for elem in cursor) - - @register_query(MongoDBConnection) def get_asset_by_id(conn, asset_id): cursor = conn.db['bigchain'].aggregate([ @@ -249,3 +234,19 @@ def get_unvoted_blocks(conn, node_pubkey): 'votes': False, '_id': False }} ]) + + +@register_query(MongoDBConnection) +def get_txids_filtered(conn, asset_id, operation=None): + match = {'block.transactions.asset.id': asset_id} + + if operation: + match['block.transactions.operation'] = operation + + cursor = conn.db['bigchain'].aggregate([ + {'$match': match}, + {'$unwind': '$block.transactions'}, + {'$match': match}, + {'$project': {'block.transactions.id': True}} + ]) + return (r['block']['transactions']['id'] for r in cursor) diff --git a/bigchaindb/backend/query.py b/bigchaindb/backend/query.py index e71f6be3..cbd17d25 100644 --- a/bigchaindb/backend/query.py +++ b/bigchaindb/backend/query.py @@ -318,3 +318,16 @@ def get_unvoted_blocks(connection, node_pubkey): """ raise NotImplementedError + + +@singledispatch +def get_txids_filtered(connection, asset_id, operation=None): + """ + Return all transactions for a particular asset id and optional operation. + + Args: + asset_id (str): ID of transaction that defined the asset + operation (str) (optional): Operation to filter on + """ + + raise NotImplementedError diff --git a/bigchaindb/backend/rethinkdb/query.py b/bigchaindb/backend/rethinkdb/query.py index f7a7c45a..fd0bdcb3 100644 --- a/bigchaindb/backend/rethinkdb/query.py +++ b/bigchaindb/backend/rethinkdb/query.py @@ -71,22 +71,6 @@ def get_blocks_status_from_transaction(connection, transaction_id): .pluck('votes', 'id', {'block': ['voters']})) -@register_query(RethinkDBConnection) -def get_txids_by_asset_id(connection, asset_id): - # here we only want to return the transaction ids since later on when - # we are going to retrieve the transaction with status validation - - # Then find any TRANSFER transactions related to the asset - tx_cursor = connection.run( - r.table('bigchain') - .get_all(asset_id, index='asset_id') - .concat_map(lambda block: block['block']['transactions']) - .filter(lambda transaction: transaction['asset']['id'] == asset_id) - .get_field('id')) - - return tx_cursor - - @register_query(RethinkDBConnection) def get_asset_by_id(connection, asset_id): return connection.run(_get_asset_create_tx_query(asset_id).pluck('asset')) @@ -249,3 +233,20 @@ def get_unvoted_blocks(connection, node_pubkey): # database level. Solving issue #444 can help untangling the situation unvoted_blocks = filter(lambda block: not utils.is_genesis_block(block), unvoted) return unvoted_blocks + + +@register_query(RethinkDBConnection) +def get_txids_filtered(connection, asset_id, operation=None): + # here we only want to return the transaction ids since later on when + # we are going to retrieve the transaction with status validation + + tx_filter = r.row['asset']['id'] == asset_id + if operation: + tx_filter &= r.row['operation'] == operation + + return connection.run( + r.table('bigchain') + .get_all(asset_id, index='asset_id') + .concat_map(lambda block: block['block']['transactions']) + .filter(tx_filter) + .get_field('id')) diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 3c62e65d..a520bba4 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -317,30 +317,6 @@ class Bigchain(object): else: return None - def get_transactions_by_asset_id(self, asset_id): - """Retrieves valid or undecided transactions related to a particular - asset. - - A digital asset in bigchaindb is identified by an uuid. This allows us - to query all the transactions related to a particular digital asset, - knowing the id. - - Args: - asset_id (str): the id for this particular asset. - - Returns: - A list of valid or undecided transactions related to the asset. - If no transaction exists for that asset it returns an empty list - `[]` - """ - txids = backend.query.get_txids_by_asset_id(self.connection, asset_id) - transactions = [] - for txid in txids: - tx = self.get_transaction(txid) - if tx: - transactions.append(tx) - return transactions - def get_asset_by_id(self, asset_id): """Returns the asset associated with an asset_id. diff --git a/tests/assets/test_digital_assets.py b/tests/assets/test_digital_assets.py index 9d2adbd5..1dc4764f 100644 --- a/tests/assets/test_digital_assets.py +++ b/tests/assets/test_digital_assets.py @@ -90,95 +90,6 @@ def test_asset_id_mismatch(b, user_pk): Transaction.get_asset_id([tx1, tx2]) -@pytest.mark.bdb -@pytest.mark.usefixtures('inputs') -def test_get_transactions_by_asset_id(b, user_pk, user_sk): - from bigchaindb.models import Transaction - - tx_create = b.get_owned_ids(user_pk).pop() - tx_create = b.get_transaction(tx_create.txid) - asset_id = tx_create.id - txs = b.get_transactions_by_asset_id(asset_id) - - assert len(txs) == 1 - assert txs[0].id == tx_create.id - assert txs[0].id == asset_id - - # create a transfer transaction - tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_pk], 1)], - tx_create.id) - tx_transfer_signed = tx_transfer.sign([user_sk]) - # create the block - block = b.create_block([tx_transfer_signed]) - b.write_block(block) - # vote the block valid - vote = b.vote(block.id, b.get_last_voted_block().id, True) - b.write_vote(vote) - - txs = b.get_transactions_by_asset_id(asset_id) - - assert len(txs) == 2 - assert {tx_create.id, tx_transfer.id} == set(tx.id for tx in txs) - assert asset_id == Transaction.get_asset_id(txs) - - -@pytest.mark.bdb -@pytest.mark.usefixtures('inputs') -def test_get_transactions_by_asset_id_with_invalid_block(b, user_pk, user_sk): - from bigchaindb.models import Transaction - - tx_create = b.get_owned_ids(user_pk).pop() - tx_create = b.get_transaction(tx_create.txid) - asset_id = tx_create.id - txs = b.get_transactions_by_asset_id(asset_id) - - assert len(txs) == 1 - assert txs[0].id == tx_create.id - assert txs[0].id == asset_id - - # create a transfer transaction - tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_pk], 1)], - tx_create.id) - tx_transfer_signed = tx_transfer.sign([user_sk]) - # create the block - block = b.create_block([tx_transfer_signed]) - b.write_block(block) - # vote the block invalid - vote = b.vote(block.id, b.get_last_voted_block().id, False) - b.write_vote(vote) - - txs = b.get_transactions_by_asset_id(asset_id) - - assert len(txs) == 1 - - -@pytest.mark.bdb -@pytest.mark.usefixtures('inputs') -def test_get_asset_by_id(b, user_pk, user_sk): - from bigchaindb.models import Transaction - - tx_create = b.get_owned_ids(user_pk).pop() - tx_create = b.get_transaction(tx_create.txid) - - # create a transfer transaction - tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_pk], 1)], - tx_create.id) - tx_transfer_signed = tx_transfer.sign([user_sk]) - # create the block - block = b.create_block([tx_transfer_signed]) - b.write_block(block) - # vote the block valid - vote = b.vote(block.id, b.get_last_voted_block().id, True) - b.write_vote(vote) - - asset_id = Transaction.get_asset_id([tx_create, tx_transfer]) - txs = b.get_transactions_by_asset_id(asset_id) - assert len(txs) == 2 - - asset = b.get_asset_by_id(asset_id) - assert asset == tx_create.asset - - def test_create_invalid_divisible_asset(b, user_pk, user_sk): from bigchaindb.models import Transaction from bigchaindb.common.exceptions import AmountError diff --git a/tests/backend/mongodb/test_queries.py b/tests/backend/mongodb/test_queries.py index 9f20cc9f..48089805 100644 --- a/tests/backend/mongodb/test_queries.py +++ b/tests/backend/mongodb/test_queries.py @@ -125,24 +125,6 @@ def test_get_block_status_from_transaction(create_tx): assert block_db['block']['voters'] == block.voters -def test_get_txids_by_asset_id(signed_create_tx, signed_transfer_tx): - from bigchaindb.backend import connect, query - from bigchaindb.models import Block - conn = connect() - - # create and insert two blocks, one for the create and one for the - # transfer transaction - block = Block(transactions=[signed_create_tx]) - conn.db.bigchain.insert_one(block.to_dict()) - block = Block(transactions=[signed_transfer_tx]) - conn.db.bigchain.insert_one(block.to_dict()) - - txids = list(query.get_txids_by_asset_id(conn, signed_create_tx.id)) - - assert len(txids) == 2 - assert txids == [signed_create_tx.id, signed_transfer_tx.id] - - def test_get_asset_by_id(create_tx): from bigchaindb.backend import connect, query from bigchaindb.models import Block @@ -366,3 +348,30 @@ def test_get_unvoted_blocks(signed_create_tx): assert len(unvoted_blocks) == 1 assert unvoted_blocks[0] == block.to_dict() + + +def test_get_txids_filtered(signed_create_tx, signed_transfer_tx): + from bigchaindb.backend import connect, query + from bigchaindb.models import Block, Transaction + conn = connect() + + # create and insert two blocks, one for the create and one for the + # transfer transaction + block = Block(transactions=[signed_create_tx]) + conn.db.bigchain.insert_one(block.to_dict()) + block = Block(transactions=[signed_transfer_tx]) + conn.db.bigchain.insert_one(block.to_dict()) + + asset_id = Transaction.get_asset_id([signed_create_tx, signed_transfer_tx]) + + # Test get by just asset id + txids = set(query.get_txids_filtered(conn, asset_id)) + assert txids == {signed_create_tx.id, signed_transfer_tx.id} + + # Test get by asset and CREATE + txids = set(query.get_txids_filtered(conn, asset_id, Transaction.CREATE)) + assert txids == {signed_create_tx.id} + + # Test get by asset and TRANSFER + txids = set(query.get_txids_filtered(conn, asset_id, Transaction.TRANSFER)) + assert txids == {signed_transfer_tx.id} diff --git a/tests/backend/test_generics.py b/tests/backend/test_generics.py index 2ab33a7c..8db08999 100644 --- a/tests/backend/test_generics.py +++ b/tests/backend/test_generics.py @@ -26,7 +26,7 @@ def test_schema(schema_func_name, args_qty): ('get_stale_transactions', 1), ('get_blocks_status_from_transaction', 1), ('get_transaction_from_backlog', 1), - ('get_txids_by_asset_id', 1), + ('get_txids_filtered', 1), ('get_asset_by_id', 1), ('get_owned_ids', 1), ('get_votes_by_block_id', 1), From 1c918caf4c59d0660678045e567183927c9d89fa Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Wed, 18 Jan 2017 11:23:17 +0100 Subject: [PATCH 3/9] txlist / get_transactions_filtered --- bigchaindb/core.py | 13 +++++++++ tests/test_txlist.py | 66 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 tests/test_txlist.py diff --git a/bigchaindb/core.py b/bigchaindb/core.py index a520bba4..e18ec46d 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -417,6 +417,19 @@ class Bigchain(object): return owned + def get_transactions_filtered(self, asset_id=None, operation=None): + """ + Get a list of transactions filtered on some criteria + """ + if not asset_id: + raise ValueError("Need asset_id") + txids = backend.query.get_txids_filtered(self.connection, asset_id, + operation) + for txid in txids: + tx, status = self.get_transaction(txid, True) + if status == self.TX_VALID: + yield tx + def create_block(self, validated_transactions): """Creates a block given a list of `validated_transactions`. diff --git a/tests/test_txlist.py b/tests/test_txlist.py new file mode 100644 index 00000000..6f9f203a --- /dev/null +++ b/tests/test_txlist.py @@ -0,0 +1,66 @@ +""" +Test getting a list of transactions from the backend. + +This test module defines it's own fixture which is used by all the tests. +""" +import pytest + + +@pytest.fixture +def txlist(b, user_pk, user2_pk, user_sk, user2_sk, genesis_block): + from bigchaindb.models import Transaction + prev_block_id = genesis_block.id + + # Create first block with CREATE transactions + create1 = Transaction.create([user_pk], [([user2_pk], 6)]) \ + .sign([user_sk]) + create2 = Transaction.create([user2_pk], + [([user2_pk], 5), ([user_pk], 5)]) \ + .sign([user2_sk]) + block1 = b.create_block([create1, create2]) + b.write_block(block1) + + # Create second block with TRANSFER transactions + transfer1 = Transaction.transfer(create1.to_inputs(), + [([user_pk], 8)], + create1.id).sign([user2_sk]) + block2 = b.create_block([transfer1]) + b.write_block(block2) + + # Create block with double spend + tx_doublespend = Transaction.transfer(create1.to_inputs(), + [([user_pk], 9)], + create1.id).sign([user2_sk]) + block_doublespend = b.create_block([tx_doublespend]) + b.write_block(block_doublespend) + + # Vote on all the blocks + prev_block_id = genesis_block.id + for bid in [block1.id, block2.id]: + vote = b.vote(bid, prev_block_id, True) + prev_block_id = bid + b.write_vote(vote) + + # Create undecided block + untx = Transaction.create([user_pk], [([user2_pk], 7)]) \ + .sign([user_sk]) + block_undecided = b.create_block([untx]) + b.write_block(block_undecided) + + return type('', (), { + 'create1': create1, + 'transfer1': transfer1, + }) + + +@pytest.mark.bdb +def test_get_txlist_by_asset(b, txlist): + res = b.get_transactions_filtered(txlist.create1.id) + assert set(tx.id for tx in res) == set([txlist.transfer1.id, + txlist.create1.id]) + + +@pytest.mark.bdb +def test_get_txlist_by_operation(b, txlist): + res = b.get_transactions_filtered(txlist.create1.id, operation='CREATE') + assert set(tx.id for tx in res) == {txlist.create1.id} From ae14d0e5c4ffff38a839acfb57c38f15d17c7abf Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Wed, 18 Jan 2017 15:36:46 +0100 Subject: [PATCH 4/9] api method to get transactions by asset_id --- bigchaindb/web/views/transactions.py | 12 ++++++---- tests/web/test_transactions.py | 36 ++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/bigchaindb/web/views/transactions.py b/bigchaindb/web/views/transactions.py index e61a791f..bc828df7 100644 --- a/bigchaindb/web/views/transactions.py +++ b/bigchaindb/web/views/transactions.py @@ -57,12 +57,14 @@ class TransactionListApi(Resource): def get(self): parser = reqparse.RequestParser() parser.add_argument('operation', type=parameters.valid_operation) - parser.add_argument('unspent', type=parameters.valid_bool) - parser.add_argument('public_key', type=parameters.valid_ed25519, - action="append") - parser.add_argument('asset_id', type=parameters.valid_txid) + parser.add_argument('asset_id', type=parameters.valid_txid, + required=True) args = parser.parse_args() - return args + + with current_app.config['bigchain_pool']() as bigchain: + txes = bigchain.get_transactions_filtered(**args) + + return [tx.to_dict() for tx in txes] def post(self): """API endpoint to push transactions to the Federation. diff --git a/tests/web/test_transactions.py b/tests/web/test_transactions.py index 9b7fac3f..f40bc924 100644 --- a/tests/web/test_transactions.py +++ b/tests/web/test_transactions.py @@ -1,5 +1,6 @@ import builtins import json +from unittest.mock import Mock, patch import pytest from bigchaindb.common import crypto @@ -180,3 +181,38 @@ def test_post_invalid_transfer_transaction_returns_400(b, client, user_pk): InvalidSignature.__name__, 'Transaction signature is invalid.') assert res.status_code == expected_status_code assert res.json['message'] == expected_error_message + + +def test_transactions_get_list_good(client): + from functools import partial + def gtf(conn, **args): + return [type('', (), {'to_dict': partial(lambda a: a, arg)}) + for arg in sorted(args.items())] + + asset_id = '1' * 64 + + with patch('bigchaindb.core.Bigchain.get_transactions_filtered', gtf): + url = TX_ENDPOINT + "?asset_id=" + asset_id + assert client.get(url).json == [ + ['asset_id', asset_id], + ['operation', None] + ] + url = TX_ENDPOINT + "?asset_id=" + asset_id + "&operation=CREATE" + assert client.get(url).json == [ + ['asset_id', asset_id], + ['operation', 'CREATE'] + ] + + +def test_transactions_get_list_bad(client): + with patch('bigchaindb.core.Bigchain.get_transactions_filtered', + lambda *_, **__: should_not_be_called()): + # Test asset id validated + url = TX_ENDPOINT + "?asset_id=" + '1' * 63 + assert client.get(url).status_code == 400 + # Test operation validated + url = TX_ENDPOINT + "?asset_id=" + '1' * 64 + "&operation=CEATE" + assert client.get(url).status_code == 400 + # Test asset ID required + url = TX_ENDPOINT + "?operation=CREATE" + assert client.get(url).status_code == 400 From 2ec23be05d5104b0a8c3aee14e9c3698228bdd8a Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Wed, 18 Jan 2017 17:13:38 +0100 Subject: [PATCH 5/9] fix flake8 --- bigchaindb/web/views/parameters.py | 2 +- bigchaindb/web/views/transactions.py | 1 - tests/test_txlist.py | 7 +++---- tests/web/test_parameters.py | 4 ++-- tests/web/test_transactions.py | 5 ++++- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/bigchaindb/web/views/parameters.py b/bigchaindb/web/views/parameters.py index 9968a659..222adb97 100644 --- a/bigchaindb/web/views/parameters.py +++ b/bigchaindb/web/views/parameters.py @@ -17,7 +17,7 @@ def valid_bool(val): def valid_ed25519(key): if (re.match('^[1-9a-zA-Z]{43,44}$', key) and not - re.match('.*[Il0O]', key)): + re.match('.*[Il0O]', key)): return key raise ValueError("Invalid base58 ed25519 key") diff --git a/bigchaindb/web/views/transactions.py b/bigchaindb/web/views/transactions.py index bc828df7..a2b6cd30 100644 --- a/bigchaindb/web/views/transactions.py +++ b/bigchaindb/web/views/transactions.py @@ -5,7 +5,6 @@ For more information please refer to the documentation on ReadTheDocs: http-client-server-api.html """ import logging -import re from flask import current_app, request from flask_restful import Resource, reqparse diff --git a/tests/test_txlist.py b/tests/test_txlist.py index 6f9f203a..e7de8c98 100644 --- a/tests/test_txlist.py +++ b/tests/test_txlist.py @@ -28,9 +28,8 @@ def txlist(b, user_pk, user2_pk, user_sk, user2_sk, genesis_block): b.write_block(block2) # Create block with double spend - tx_doublespend = Transaction.transfer(create1.to_inputs(), - [([user_pk], 9)], - create1.id).sign([user2_sk]) + tx_doublespend = Transaction.transfer(create1.to_inputs(), [([user_pk], 9)], + create1.id).sign([user2_sk]) block_doublespend = b.create_block([tx_doublespend]) b.write_block(block_doublespend) @@ -43,7 +42,7 @@ def txlist(b, user_pk, user2_pk, user_sk, user2_sk, genesis_block): # Create undecided block untx = Transaction.create([user_pk], [([user2_pk], 7)]) \ - .sign([user_sk]) + .sign([user_sk]) block_undecided = b.create_block([untx]) b.write_block(block_undecided) diff --git a/tests/web/test_parameters.py b/tests/web/test_parameters.py index 4044a273..d39c6f38 100644 --- a/tests/web/test_parameters.py +++ b/tests/web/test_parameters.py @@ -22,8 +22,8 @@ def test_valid_txid(): def test_valid_bool(): from bigchaindb.web.views.parameters import valid_bool - assert valid_bool('true') == True - valid_bool('false') == False + assert valid_bool('true') is True + assert valid_bool('false') is False with pytest.raises(ValueError): valid_bool('TRUE') diff --git a/tests/web/test_transactions.py b/tests/web/test_transactions.py index f40bc924..fb0f8cb2 100644 --- a/tests/web/test_transactions.py +++ b/tests/web/test_transactions.py @@ -1,6 +1,6 @@ import builtins import json -from unittest.mock import Mock, patch +from unittest.mock import patch import pytest from bigchaindb.common import crypto @@ -185,6 +185,7 @@ def test_post_invalid_transfer_transaction_returns_400(b, client, user_pk): def test_transactions_get_list_good(client): from functools import partial + def gtf(conn, **args): return [type('', (), {'to_dict': partial(lambda a: a, arg)}) for arg in sorted(args.items())] @@ -205,6 +206,8 @@ def test_transactions_get_list_good(client): def test_transactions_get_list_bad(client): + def should_not_be_called(): + assert False with patch('bigchaindb.core.Bigchain.get_transactions_filtered', lambda *_, **__: should_not_be_called()): # Test asset id validated From f2034cddde399973c73015a7c149d727af6c84ca Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Fri, 20 Jan 2017 10:08:30 +0100 Subject: [PATCH 6/9] make asset_id not optional in get_transactions_filtered --- bigchaindb/core.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bigchaindb/core.py b/bigchaindb/core.py index e18ec46d..f9c96bed 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -417,12 +417,10 @@ class Bigchain(object): return owned - def get_transactions_filtered(self, asset_id=None, operation=None): + def get_transactions_filtered(self, asset_id, operation=None): """ Get a list of transactions filtered on some criteria """ - if not asset_id: - raise ValueError("Need asset_id") txids = backend.query.get_txids_filtered(self.connection, asset_id, operation) for txid in txids: From c5253825b3bcc527fa21b71e099a7962a3dc22a4 Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Fri, 20 Jan 2017 10:31:28 +0100 Subject: [PATCH 7/9] remove unused backend query stub get_txids_by_asset_id --- bigchaindb/backend/query.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/bigchaindb/backend/query.py b/bigchaindb/backend/query.py index cbd17d25..bdfb9e28 100644 --- a/bigchaindb/backend/query.py +++ b/bigchaindb/backend/query.py @@ -107,25 +107,6 @@ def get_blocks_status_from_transaction(connection, transaction_id): raise NotImplementedError -@singledispatch -def get_txids_by_asset_id(connection, asset_id): - """Retrieves transactions ids related to a particular asset. - - A digital asset in bigchaindb is identified by its ``CREATE`` - transaction's ID. Knowing this ID allows us to query all the - transactions related to a particular digital asset. - - Args: - asset_id (str): the ID of the asset. - - Returns: - A list of transactions ids related to the asset. If no transaction - exists for that asset it returns an empty list ``[]`` - """ - - raise NotImplementedError - - @singledispatch def get_asset_by_id(conneciton, asset_id): """Returns the asset associated with an asset_id. From a5acd0c7b9ce03f2e8e9be1208ae95560cd877e4 Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Mon, 23 Jan 2017 14:07:22 +0100 Subject: [PATCH 8/9] document patching technique in get_transactions_filtered web view test --- tests/web/test_transactions.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/web/test_transactions.py b/tests/web/test_transactions.py index fb0f8cb2..948ef171 100644 --- a/tests/web/test_transactions.py +++ b/tests/web/test_transactions.py @@ -186,13 +186,17 @@ def test_post_invalid_transfer_transaction_returns_400(b, client, user_pk): def test_transactions_get_list_good(client): from functools import partial - def gtf(conn, **args): + def get_txs_patched(conn, **args): + """ Patch `get_transactions_filtered` so that rather than return an array + of transactions it returns an array of shims with a to_dict() method + that reports one of the arguments passed to `get_transactions_filtered`. + """ return [type('', (), {'to_dict': partial(lambda a: a, arg)}) for arg in sorted(args.items())] asset_id = '1' * 64 - with patch('bigchaindb.core.Bigchain.get_transactions_filtered', gtf): + with patch('bigchaindb.core.Bigchain.get_transactions_filtered', get_txs_patched): url = TX_ENDPOINT + "?asset_id=" + asset_id assert client.get(url).json == [ ['asset_id', asset_id], From 23ba642d2cb571859bdce11543876ade0ee9eeff Mon Sep 17 00:00:00 2001 From: libscott Date: Mon, 23 Jan 2017 14:38:30 +0100 Subject: [PATCH 9/9] s/txes/txs/g --- bigchaindb/web/views/transactions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bigchaindb/web/views/transactions.py b/bigchaindb/web/views/transactions.py index a2b6cd30..3059b34f 100644 --- a/bigchaindb/web/views/transactions.py +++ b/bigchaindb/web/views/transactions.py @@ -61,9 +61,9 @@ class TransactionListApi(Resource): args = parser.parse_args() with current_app.config['bigchain_pool']() as bigchain: - txes = bigchain.get_transactions_filtered(**args) + txs = bigchain.get_transactions_filtered(**args) - return [tx.to_dict() for tx in txes] + return [tx.to_dict() for tx in txs] def post(self): """API endpoint to push transactions to the Federation.