bigchaindb/tests/backend/mongodb/test_queries.py

611 lines
20 KiB
Python

from copy import deepcopy
import pytest
from unittest import mock
import pymongo
pytestmark = pytest.mark.bdb
def test_write_transaction(signed_create_tx):
from bigchaindb.backend import connect, query
conn = connect()
# write the transaction
query.write_transaction(conn, signed_create_tx.to_dict())
# get the transaction
tx_db = conn.db.backlog.find_one({'id': signed_create_tx.id},
{'_id': False})
assert tx_db == signed_create_tx.to_dict()
def test_update_transaction(signed_create_tx):
from bigchaindb.backend import connect, query
conn = connect()
# update_transaction can update any field we want, but lets update the
# same fields that are updated by bigchaindb core.
signed_create_tx = signed_create_tx.to_dict()
signed_create_tx.update({'assignee': 'aaa', 'assignment_timestamp': 10})
conn.db.backlog.insert_one(signed_create_tx)
query.update_transaction(conn, signed_create_tx['id'],
{'assignee': 'bbb', 'assignment_timestamp': 20})
tx_db = conn.db.backlog.find_one({'id': signed_create_tx['id']},
{'_id': False})
assert tx_db['assignee'] == 'bbb'
assert tx_db['assignment_timestamp'] == 20
def test_delete_transaction(signed_create_tx):
from bigchaindb.backend import connect, query
conn = connect()
# write_the transaction
result = conn.db.backlog.insert_one(signed_create_tx.to_dict())
# delete transaction
query.delete_transaction(conn, signed_create_tx.id)
tx_db = conn.db.backlog.find_one({'_id': result.inserted_id})
assert tx_db is None
def test_get_stale_transactions(signed_create_tx):
import time
from bigchaindb.backend import connect, query
conn = connect()
# create two transaction, one of them stale
tx1 = signed_create_tx.to_dict()
tx1.update({'id': 'notstale', 'assignment_timestamp': time.time()})
tx2 = signed_create_tx.to_dict()
tx2.update({'id': 'stale', 'assignment_timestamp': time.time() - 60})
# write the transactions
conn.db.backlog.insert_one(tx1)
conn.db.backlog.insert_one(tx2)
# get stale transactions
stale_txs = list(query.get_stale_transactions(conn, 30))
assert len(stale_txs) == 1
assert stale_txs[0]['id'] == 'stale'
def test_get_transaction_from_block(user_pk):
from bigchaindb.backend import connect, query
from bigchaindb.models import Transaction, Block
conn = connect()
# create a block with 2 transactions
txs = [
Transaction.create([user_pk], [([user_pk], 1)]),
Transaction.create([user_pk], [([user_pk], 1)]),
]
block = Block(transactions=txs)
conn.db.bigchain.insert_one(block.to_dict())
tx_db = query.get_transaction_from_block(conn, txs[0].id, block.id)
assert tx_db == txs[0].to_dict()
assert query.get_transaction_from_block(conn, txs[0].id, 'aaa') is None
assert query.get_transaction_from_block(conn, 'aaa', block.id) is None
def test_get_transaction_from_backlog(create_tx):
from bigchaindb.backend import connect, query
conn = connect()
# insert transaction
conn.db.backlog.insert_one(create_tx.to_dict())
# query the backlog
tx_db = query.get_transaction_from_backlog(conn, create_tx.id)
assert tx_db == create_tx.to_dict()
def test_get_block_status_from_transaction(create_tx):
from bigchaindb.backend import connect, query
from bigchaindb.models import Block
conn = connect()
# create a block
block = Block(transactions=[create_tx], voters=['aaa', 'bbb', 'ccc'])
# insert block
conn.db.bigchain.insert_one(block.to_dict())
block_db = list(query.get_blocks_status_from_transaction(conn,
create_tx.id))
assert len(block_db) == 1
block_db = block_db.pop()
assert block_db['id'] == block.id
assert block_db['block']['voters'] == block.voters
def test_get_asset_by_id(create_tx):
from bigchaindb.backend import connect, query
from bigchaindb.models import Block
conn = connect()
# create asset and block
create_tx.asset = {'msg': 'aaa'}
block = Block(transactions=[create_tx])
conn.db.bigchain.insert_one(block.to_dict())
asset = list(query.get_asset_by_id(conn, create_tx.id))
assert len(asset) == 1
assert asset[0]['asset'] == create_tx.asset
def test_get_spent(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())
spents = list(query.get_spent(conn, signed_create_tx.id, 0))
assert len(spents) == 1
assert spents[0] == signed_transfer_tx.to_dict()
def test_get_spent_for_tx_with_multiple_inputs(carol):
from bigchaindb.backend import connect, query
from bigchaindb.models import Block, Transaction
conn = connect()
tx_0 = Transaction.create(
[carol.public_key],
[([carol.public_key], 1),
([carol.public_key], 1),
([carol.public_key], 2)],
).sign([carol.private_key])
block = Block(transactions=[tx_0])
conn.db.bigchain.insert_one(block.to_dict())
spents = list(query.get_spent(conn, tx_0.id, 0))
assert not spents
tx_1 = Transaction.transfer(
tx_0.to_inputs()[2:3],
[([carol.public_key], 1),
([carol.public_key], 1)],
asset_id=tx_0.id,
).sign([carol.private_key])
block = Block(transactions=[tx_1])
conn.db.bigchain.insert_one(block.to_dict())
spents = list(query.get_spent(conn, tx_0.id, 0))
assert not spents
tx_2 = Transaction.transfer(
tx_0.to_inputs()[0:1] + tx_1.to_inputs()[1:2],
[([carol.public_key], 2)],
asset_id=tx_0.id,
).sign([carol.private_key])
block = Block(transactions=[tx_2])
conn.db.bigchain.insert_one(block.to_dict())
spents = list(query.get_spent(conn, tx_0.id, 1))
assert not spents
def test_get_owned_ids(signed_create_tx, user_pk):
from bigchaindb.backend import connect, query
from bigchaindb.models import Block
conn = connect()
# create and insert a block
block = Block(transactions=[signed_create_tx])
conn.db.bigchain.insert_one(block.to_dict())
[(block_id, tx)] = list(query.get_owned_ids(conn, user_pk))
assert block_id == block.id
assert tx == signed_create_tx.to_dict()
def test_get_votes_by_block_id(signed_create_tx, structurally_valid_vote):
from bigchaindb.common.crypto import generate_key_pair
from bigchaindb.backend import connect, query
from bigchaindb.models import Block
conn = connect()
# create and insert a block
block = Block(transactions=[signed_create_tx])
conn.db.bigchain.insert_one(block.to_dict())
# create and insert some votes
structurally_valid_vote['vote']['voting_for_block'] = block.id
conn.db.votes.insert_one(structurally_valid_vote)
# create a second vote under a different key
_, pk = generate_key_pair()
structurally_valid_vote['vote']['voting_for_block'] = block.id
structurally_valid_vote['node_pubkey'] = pk
structurally_valid_vote.pop('_id')
conn.db.votes.insert_one(structurally_valid_vote)
votes = list(query.get_votes_by_block_id(conn, block.id))
assert len(votes) == 2
assert votes[0]['vote']['voting_for_block'] == block.id
assert votes[1]['vote']['voting_for_block'] == block.id
def test_get_votes_by_block_id_and_voter(signed_create_tx,
structurally_valid_vote):
from bigchaindb.backend import connect, query
from bigchaindb.models import Block
conn = connect()
# create and insert a block
block = Block(transactions=[signed_create_tx])
conn.db.bigchain.insert_one(block.to_dict())
# create and insert some votes
structurally_valid_vote['vote']['voting_for_block'] = block.id
structurally_valid_vote['node_pubkey'] = 'aaa'
conn.db.votes.insert_one(structurally_valid_vote)
structurally_valid_vote['vote']['voting_for_block'] = block.id
structurally_valid_vote['node_pubkey'] = 'bbb'
structurally_valid_vote.pop('_id')
conn.db.votes.insert_one(structurally_valid_vote)
votes = list(query.get_votes_by_block_id_and_voter(conn, block.id, 'aaa'))
assert len(votes) == 1
assert votes[0]['node_pubkey'] == 'aaa'
def test_write_block(signed_create_tx):
from bigchaindb.backend import connect, query
from bigchaindb.models import Block
conn = connect()
# create and write block
block = Block(transactions=[signed_create_tx])
query.write_block(conn, block.to_dict())
block_db = conn.db.bigchain.find_one({'id': block.id}, {'_id': False})
assert block_db == block.to_dict()
def test_get_block(signed_create_tx):
from bigchaindb.backend import connect, query
from bigchaindb.models import Block
conn = connect()
# create and insert block
block = Block(transactions=[signed_create_tx])
conn.db.bigchain.insert_one(block.to_dict())
block_db = query.get_block(conn, block.id)
assert block_db == block.to_dict()
def test_count_blocks(signed_create_tx):
from bigchaindb.backend import connect, query
from bigchaindb.models import Block
conn = connect()
# create and insert some blocks
block = Block(transactions=[signed_create_tx])
conn.db.bigchain.insert_one(block.to_dict())
conn.db.bigchain.insert_one(block.to_dict())
assert query.count_blocks(conn) == 2
def test_count_backlog(signed_create_tx):
from bigchaindb.backend import connect, query
conn = connect()
# create and insert some transations
conn.db.backlog.insert_one(signed_create_tx.to_dict())
signed_create_tx.metadata = {'msg': 'aaa'}
conn.db.backlog.insert_one(signed_create_tx.to_dict())
assert query.count_backlog(conn) == 2
def test_write_vote(structurally_valid_vote):
from bigchaindb.backend import connect, query
conn = connect()
# write a vote
query.write_vote(conn, structurally_valid_vote)
# retrieve the vote
vote_db = conn.db.votes.find_one(
{'node_pubkey': structurally_valid_vote['node_pubkey']},
{'_id': False}
)
assert vote_db == structurally_valid_vote
def test_duplicate_vote_raises_duplicate_key(structurally_valid_vote):
from bigchaindb.backend import connect, query
from bigchaindb.backend.exceptions import DuplicateKeyError
conn = connect()
# write a vote
query.write_vote(conn, structurally_valid_vote)
# write the same vote a second time
with pytest.raises(DuplicateKeyError):
query.write_vote(conn, structurally_valid_vote)
def test_get_genesis_block(genesis_block):
from bigchaindb.backend import connect, query
conn = connect()
assets, genesis_block_dict = genesis_block.decouple_assets()
assert query.get_genesis_block(conn) == genesis_block_dict
def test_get_last_voted_block_id(genesis_block, signed_create_tx, b):
from bigchaindb.backend import connect, query
from bigchaindb.models import Block
from bigchaindb.common.exceptions import CyclicBlockchainError
conn = connect()
# check that the last voted block is the genesis block
assert query.get_last_voted_block_id(conn, b.me) == genesis_block.id
# create and insert a new vote and block
block = Block(transactions=[signed_create_tx])
conn.db.bigchain.insert_one(block.to_dict())
vote = b.vote(block.id, genesis_block.id, True)
conn.db.votes.insert_one(vote)
assert query.get_last_voted_block_id(conn, b.me) == block.id
# force a bad chain
vote.pop('_id')
vote['vote']['voting_for_block'] = genesis_block.id
vote['vote']['previous_block'] = block.id
conn.db.votes.insert_one(vote)
with pytest.raises(CyclicBlockchainError):
query.get_last_voted_block_id(conn, b.me)
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}
@mock.patch('bigchaindb.backend.mongodb.changefeed._FEED_STOP', True)
def test_get_new_blocks_feed(b, create_tx):
from bigchaindb.backend import query
from bigchaindb.models import Block
import random
def create_block():
ts = str(random.random())
block = Block(transactions=[create_tx], timestamp=ts)
b.write_block(block)
return block.decouple_assets()[1]
create_block()
b1 = create_block()
b2 = create_block()
feed = query.get_new_blocks_feed(b.connection, b1['id'])
assert feed.__next__() == b2
b3 = create_block()
assert list(feed) == [b3]
def test_get_spending_transactions(user_pk):
from bigchaindb.backend import connect, query
from bigchaindb.models import Block, Transaction
conn = connect()
out = [([user_pk], 1)]
tx1 = Transaction.create([user_pk], out * 3)
inputs = tx1.to_inputs()
tx2 = Transaction.transfer([inputs[0]], out, tx1.id)
tx3 = Transaction.transfer([inputs[1]], out, tx1.id)
tx4 = Transaction.transfer([inputs[2]], out, tx1.id)
block = Block([tx1, tx2, tx3, tx4])
conn.db.bigchain.insert_one(block.to_dict())
links = [inputs[0].fulfills.to_dict(), inputs[2].fulfills.to_dict()]
res = list(query.get_spending_transactions(conn, links))
# tx3 not a member because input 1 not asked for
assert res == [(block.id, tx2.to_dict()), (block.id, tx4.to_dict())]
def test_get_votes_for_blocks_by_voter():
from bigchaindb.backend import connect, query
conn = connect()
votes = [
{
'node_pubkey': 'a',
'vote': {'voting_for_block': 'block1'},
},
{
'node_pubkey': 'b',
'vote': {'voting_for_block': 'block1'},
},
{
'node_pubkey': 'a',
'vote': {'voting_for_block': 'block2'},
},
{
'node_pubkey': 'a',
'vote': {'voting_for_block': 'block3'},
}
]
for vote in votes:
conn.db.votes.insert_one(vote.copy())
res = query.get_votes_for_blocks_by_voter(conn, ['block1', 'block2'], 'a')
assert list(res) == [votes[0], votes[2]]
def test_write_assets():
from bigchaindb.backend import connect, query
conn = connect()
assets = [
{'id': 1, 'data': '1'},
{'id': 2, 'data': '2'},
{'id': 3, 'data': '3'},
# Duplicated id. Should not be written to the database
{'id': 1, 'data': '1'},
]
# write the assets
query.write_assets(conn, deepcopy(assets))
# check that 3 assets were written to the database
cursor = conn.db.assets.find({}, projection={'_id': False})\
.sort('id', pymongo.ASCENDING)
assert cursor.count() == 3
assert list(cursor) == assets[:-1]
def test_get_assets():
from bigchaindb.backend import connect, query
conn = connect()
assets = [
{'id': 1, 'data': '1'},
{'id': 2, 'data': '2'},
{'id': 3, 'data': '3'},
]
# write the assets
conn.db.assets.insert_many(deepcopy(assets), ordered=False)
# read only 2 assets
cursor = query.get_assets(conn, [1, 3])
assert cursor.count() == 2
assert list(cursor.sort('id', pymongo.ASCENDING)) == assets[::2]
def test_text_search():
from bigchaindb.backend import connect, query
conn = connect()
# Example data and tests cases taken from the mongodb documentation
# https://docs.mongodb.com/manual/reference/operator/query/text/
assets = [
{'id': 1, 'subject': 'coffee', 'author': 'xyz', 'views': 50},
{'id': 2, 'subject': 'Coffee Shopping', 'author': 'efg', 'views': 5},
{'id': 3, 'subject': 'Baking a cake', 'author': 'abc', 'views': 90},
{'id': 4, 'subject': 'baking', 'author': 'xyz', 'views': 100},
{'id': 5, 'subject': 'Café Con Leche', 'author': 'abc', 'views': 200},
{'id': 6, 'subject': 'Сырники', 'author': 'jkl', 'views': 80},
{'id': 7, 'subject': 'coffee and cream', 'author': 'efg', 'views': 10},
{'id': 8, 'subject': 'Cafe con Leche', 'author': 'xyz', 'views': 10}
]
# insert the assets
conn.db.assets.insert_many(deepcopy(assets), ordered=False)
# test search single word
assert list(query.text_search(conn, 'coffee')) == [
{'id': 1, 'subject': 'coffee', 'author': 'xyz', 'views': 50},
{'id': 2, 'subject': 'Coffee Shopping', 'author': 'efg', 'views': 5},
{'id': 7, 'subject': 'coffee and cream', 'author': 'efg', 'views': 10},
]
# match any of the search terms
assert list(query.text_search(conn, 'bake coffee cake')) == [
{'author': 'abc', 'id': 3, 'subject': 'Baking a cake', 'views': 90},
{'author': 'xyz', 'id': 1, 'subject': 'coffee', 'views': 50},
{'author': 'xyz', 'id': 4, 'subject': 'baking', 'views': 100},
{'author': 'efg', 'id': 2, 'subject': 'Coffee Shopping', 'views': 5},
{'author': 'efg', 'id': 7, 'subject': 'coffee and cream', 'views': 10}
]
# search for a phrase
assert list(query.text_search(conn, '\"coffee shop\"')) == [
{'id': 2, 'subject': 'Coffee Shopping', 'author': 'efg', 'views': 5},
]
# exclude documents that contain a term
assert list(query.text_search(conn, 'coffee -shop')) == [
{'id': 1, 'subject': 'coffee', 'author': 'xyz', 'views': 50},
{'id': 7, 'subject': 'coffee and cream', 'author': 'efg', 'views': 10},
]
# search different language
assert list(query.text_search(conn, 'leche', language='es')) == [
{'id': 5, 'subject': 'Café Con Leche', 'author': 'abc', 'views': 200},
{'id': 8, 'subject': 'Cafe con Leche', 'author': 'xyz', 'views': 10}
]
# case and diacritic insensitive search
assert list(query.text_search(conn, 'сы́рники CAFÉS')) == [
{'id': 6, 'subject': 'Сырники', 'author': 'jkl', 'views': 80},
{'id': 5, 'subject': 'Café Con Leche', 'author': 'abc', 'views': 200},
{'id': 8, 'subject': 'Cafe con Leche', 'author': 'xyz', 'views': 10}
]
# case sensitive search
assert list(query.text_search(conn, 'Coffee', case_sensitive=True)) == [
{'id': 2, 'subject': 'Coffee Shopping', 'author': 'efg', 'views': 5},
]
# diacritic sensitive search
assert list(query.text_search(conn, 'CAFÉ', diacritic_sensitive=True)) == [
{'id': 5, 'subject': 'Café Con Leche', 'author': 'abc', 'views': 200},
]
# return text score
assert list(query.text_search(conn, 'coffee', text_score=True)) == [
{'id': 1, 'subject': 'coffee', 'author': 'xyz', 'views': 50, 'score': 1.0},
{'id': 2, 'subject': 'Coffee Shopping', 'author': 'efg', 'views': 5, 'score': 0.75},
{'id': 7, 'subject': 'coffee and cream', 'author': 'efg', 'views': 10, 'score': 0.75},
]
# limit search result
assert list(query.text_search(conn, 'coffee', limit=2)) == [
{'id': 1, 'subject': 'coffee', 'author': 'xyz', 'views': 50},
{'id': 2, 'subject': 'Coffee Shopping', 'author': 'efg', 'views': 5},
]