diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aac4d80d..03d02403 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,8 @@ To contribute code or documentation, you need a [GitHub account](https://github. Familiarize yourself with how we do coding and documentation in the BigchainDB project, including: -* [our Python Style Guide](PYTHON_STYLE_GUIDE.md) (includes how we write tests) +* [our Python Style Guide](PYTHON_STYLE_GUIDE.md) +* [how we write and run tests](./tests/README.md) * [our documentation strategy](./docs/README.md) (including in-code documentation) * the GitHub Flow (workflow) * [GitHub Guide: Understanding the GitHub Flow](https://guides.github.com/introduction/flow/) @@ -103,7 +104,7 @@ git checkout -b new-branch-name With your new branch checked out locally, make changes or additions to the code or documentation. Remember to: * follow [our Python Style Guide](PYTHON_STYLE_GUIDE.md). -* write and run tests for any new or changed code. There's a section in [our Python Style Guide](PYTHON_STYLE_GUIDE.md) about writing and running tests. +* write and run tests for any new or changed code. There's [a README file in the `tests/` folder](./tests/README.md) about how to do that. * add or update documentation as necessary. Follow [our documentation strategy](./docs/README.md). As you go, git add and git commit your changes or additions, e.g. @@ -119,7 +120,7 @@ git fetch upstream git merge upstream/master ``` -Once you're done commiting a set of new things and you're ready to submit them for inclusion, please be sure to run all the tests (as per the instructions at the end of our [Python Style Guide](PYTHON_STYLE_GUIDE.md)). +Once you're done commiting a set of new things and you're ready to submit them for inclusion, please be sure to run all the tests as per the instructions in [the README file in the `tests/` folder](./tests/README.md). (When you submit your pull request [following the instructions below], we run all the tests automatically, so we will see if some are failing. If you don't know why some tests are failing, you can still submit your pull request, but be sure to note the failing tests and to ask for help with resolving them.) diff --git a/PYTHON_STYLE_GUIDE.md b/PYTHON_STYLE_GUIDE.md index 42f18c85..befe4eeb 100644 --- a/PYTHON_STYLE_GUIDE.md +++ b/PYTHON_STYLE_GUIDE.md @@ -47,8 +47,15 @@ It seems the preference is for slashes, but using parentheses is okay too. (Ther If you need to `import` lots of names from a module or package, and they won't all fit in one line (without making the line too long), then use parentheses to spread the names across multiple lines, like so: ```python +from Tkinter import ( + Tk, Frame, Button, Entry, Canvas, Text, + LEFT, DISABLED, NORMAL, RIDGE, END, +) + +# Or + from Tkinter import (Tk, Frame, Button, Entry, Canvas, Text, - LEFT, DISABLED, NORMAL, RIDGE, END) + LEFT, DISABLED, NORMAL, RIDGE, END) ``` For the rationale, see [PEP 328](https://www.python.org/dev/peps/pep-0328/#rationale-for-parentheses). @@ -65,7 +72,7 @@ x = 'name: {}; score: {}'.format(name, n) we use the `format()` version. The [official Python documentation says](https://docs.python.org/2/library/stdtypes.html#str.format), "This method of string formatting is the new standard in Python 3, and should be preferred to the % formatting described in String Formatting Operations in new code." -## Runnng the Flake8 Style Checker +## Running the Flake8 Style Checker We use [Flake8](http://flake8.pycqa.org/en/latest/index.html) to check our Python code style. Once you have it installed, you can run it using: ```text @@ -75,48 +82,6 @@ flake8 --max-line-length 119 bigchaindb/ ## Writing and Running (Python) Tests -We write unit and integration tests for our Python code using the [pytest](http://pytest.org/latest/) framework. +The content of this section was moved to [`bigchiandb/tests/README.md`](./tests/README.md). -All tests go in the `bigchaindb/tests` directory or one of its subdirectories. You can use the tests already in there as templates or examples. - -You can run all tests using: -```text -py.test -v -``` - -or, if that doesn't work, try: -```text -python -m pytest -v -``` - -or: -```text -python setup.py test -``` - -If you want to learn about all the things you can do with pytest, see [the pytest documentation](http://pytest.org/latest/). - -### Tox - -We use [tox](https://tox.readthedocs.io/en/latest/) to run multiple suites of tests against multiple environments during automated testing. Generally you don't need to run this yourself, but it might be useful when troubleshooting a failing CI build. - -To run all the tox tests, use: -```text -tox -``` - -or: -```text -python -m tox -``` - -To run only a few environments, use the `-e` flag: -```text -tox -e {ENVLIST} -``` - -where `{ENVLIST}` is one or more of the environments specified in the [tox.ini file](tox.ini). - -### Automated testing of pull requests - -We use [Travis CI](https://travis-ci.com/), so that whenever someone creates a new BigchainDB pull request on GitHub, Travis CI gets the new code and does _a bunch of stuff_. You can find out what we tell Travis CI to do in [the `.travis.yml` file](.travis.yml): it tells Travis CI how to install BigchainDB, how to run all the tests, and what to do "after success" (e.g. run `codecov`). (We use [Codecov](https://codecov.io/) to get a rough estimate of our test coverage.) +Note: We automatically run all tests on all pull requests (using Travis CI), so you should definitely run all tests locally before you submit a pull request. See the above-linked README file for instructions. \ No newline at end of file diff --git a/bigchaindb/__init__.py b/bigchaindb/__init__.py index 74b87129..f6b7b6cf 100644 --- a/bigchaindb/__init__.py +++ b/bigchaindb/__init__.py @@ -17,7 +17,7 @@ config = { 'database': { 'backend': os.environ.get('BIGCHAINDB_DATABASE_BACKEND', 'rethinkdb'), 'host': os.environ.get('BIGCHAINDB_DATABASE_HOST', 'localhost'), - 'port': 28015, + 'port': int(os.environ.get('BIGCHAINDB_DATABASE_PORT', 28015)), 'name': 'bigchain', }, 'keypair': { diff --git a/bigchaindb/backend/mongodb/schema.py b/bigchaindb/backend/mongodb/schema.py index 55fd955d..7eaff782 100644 --- a/bigchaindb/backend/mongodb/schema.py +++ b/bigchaindb/backend/mongodb/schema.py @@ -1,8 +1,10 @@ """Utils to initialize and drop the database.""" +import time import logging from pymongo import ASCENDING, DESCENDING +from pymongo import errors from bigchaindb import backend from bigchaindb.common import exceptions @@ -24,6 +26,9 @@ def create_database(conn, dbname): # TODO: read and write concerns can be declared here conn.conn.get_database(dbname) + # initialize the replica set + initialize_replica_set(conn) + @register_schema(MongoDBConnection) def create_tables(conn, dbname): @@ -49,9 +54,6 @@ def drop_database(conn, dbname): def create_bigchain_secondary_index(conn, dbname): logger.info('Create `bigchain` secondary index.') - # to select blocks by id - conn.conn[dbname]['bigchain'].create_index('id', name='block_id') - # to order blocks by timestamp conn.conn[dbname]['bigchain'].create_index([('block.timestamp', ASCENDING)], @@ -62,33 +64,15 @@ def create_bigchain_secondary_index(conn, dbname): name='transaction_id', unique=True) - # secondary index for payload data by UUID, this field is unique - conn.conn[dbname]['bigchain']\ - .create_index('block.transactions.transaction.metadata.id', - name='metadata_id', unique=True) - # secondary index for asset uuid, this field is unique conn.conn[dbname]['bigchain']\ .create_index('block.transactions.transaction.asset.id', name='asset_id', unique=True) - # compound index on fulfillment and transactions id - conn.conn[dbname]['bigchain']\ - .create_index([('block.transactions.transaction.fulfillments.txid', - ASCENDING), - ('block.transactions.transaction.fulfillments.cid', - ASCENDING)], - name='tx_and_fulfillment') - def create_backlog_secondary_index(conn, dbname): logger.info('Create `backlog` secondary index.') - # to order transactions by timestamp - conn.conn[dbname]['backlog'].create_index([('transaction.timestamp', - ASCENDING)], - name='transaction_timestamp') - # compound index to read transactions from the backlog per assignee conn.conn[dbname]['backlog']\ .create_index([('assignee', ASCENDING), @@ -99,10 +83,6 @@ def create_backlog_secondary_index(conn, dbname): def create_votes_secondary_index(conn, dbname): logger.info('Create `votes` secondary index.') - # index on block id to quickly poll - conn.conn[dbname]['votes'].create_index('vote.voting_for_block', - name='voting_for') - # is the first index redundant then? # compound index to order votes by block id and node conn.conn[dbname]['votes'].create_index([('vote.voting_for_block', @@ -110,3 +90,66 @@ def create_votes_secondary_index(conn, dbname): ('node_pubkey', ASCENDING)], name='block_and_voter') + + +def initialize_replica_set(conn): + """Initialize a replica set. If already initialized skip.""" + replica_set_name = _get_replica_set_name(conn) + config = {'_id': replica_set_name, + 'members': [{'_id': 0, 'host': 'localhost:27017'}]} + + try: + conn.conn.admin.command('replSetInitiate', config) + except errors.OperationFailure as exc_info: + if exc_info.details['codeName'] == 'AlreadyInitialized': + logger.info('Replica set already initialized') + return + raise + else: + _wait_for_replica_set_initialization(conn) + logger.info('Initialized replica set') + + +def _get_replica_set_name(conn): + """Checks if the replSet option was enabled either through the command + line option or config file. + + Note: + The setting we are looking for will have a different name depending + if it was set by the config file (`replSetName`) or by command + line arguments (`replSet`). + + Returns: + The replica set name if enabled. + + Raise: + :exc:`~ConfigurationError`: If mongod was not started with the + replSet option. + """ + options = conn.conn.admin.command('getCmdLineOpts') + try: + repl_opts = options['parsed']['replication'] + return repl_opts.get('replSetName', None) or repl_opts['replSet'] + except KeyError: + raise exceptions.ConfigurationError('mongod was not started with' + ' the replSet option.') + + +def _wait_for_replica_set_initialization(conn): + """Wait for a replica set to finish initialization. + + If a replica set is being initialized for the first time it takes some + time. Nodes need to discover each other and an election needs to take + place. During this time the database is not writable so we need to wait + before continuing with the rest of the initialization + """ + + # I did not find a better way to do this for now. + # To check if the database is ready we will poll the mongodb logs until + # we find the line that says the database is ready + logger.info('Waiting for mongodb replica set initialization') + while True: + logs = conn.conn.admin.command('getLog', 'rs')['log'] + if any('database writes are now permitted' in line for line in logs): + return + time.sleep(0.1) diff --git a/bigchaindb/backend/query.py b/bigchaindb/backend/query.py index 88f215c4..e71f6be3 100644 --- a/bigchaindb/backend/query.py +++ b/bigchaindb/backend/query.py @@ -111,12 +111,12 @@ def get_blocks_status_from_transaction(connection, transaction_id): 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 an uuid. This allows us - to query all the transactions related to a particular digital asset, - knowing the id. + 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 for this particular metadata. + asset_id (str): the ID of the asset. Returns: A list of transactions ids related to the asset. If no transaction diff --git a/bigchaindb/backend/rethinkdb/query.py b/bigchaindb/backend/rethinkdb/query.py index 3bbaaeb2..9439022f 100644 --- a/bigchaindb/backend/rethinkdb/query.py +++ b/bigchaindb/backend/rethinkdb/query.py @@ -1,3 +1,4 @@ +from itertools import chain from time import time import rethinkdb as r @@ -75,23 +76,32 @@ def get_blocks_status_from_transaction(connection, transaction_id): 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 - return connection.run( + + # First find the asset's CREATE transaction + create_tx_cursor = connection.run( + _get_asset_create_tx_query(asset_id).get_field('id')) + + # Then find any TRANSFER transactions related to the asset + transfer_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 chain(create_tx_cursor, transfer_tx_cursor) + @register_query(RethinkDBConnection) def get_asset_by_id(connection, asset_id): - return connection.run( - r.table('bigchain', read_mode=READ_MODE) - .get_all(asset_id, index='asset_id') - .concat_map(lambda block: block['block']['transactions']) - .filter(lambda transaction: transaction['asset']['id'] == asset_id) - .filter(lambda transaction: transaction['operation'] == 'CREATE') - .pluck('asset')) + return connection.run(_get_asset_create_tx_query(asset_id).pluck('asset')) + + +def _get_asset_create_tx_query(asset_id): + return r.table('bigchain', read_mode=READ_MODE) \ + .get_all(asset_id, index='transaction_id') \ + .concat_map(lambda block: block['block']['transactions']) \ + .filter(lambda transaction: transaction['id'] == asset_id) @register_query(RethinkDBConnection) @@ -134,7 +144,7 @@ def get_votes_by_block_id_and_voter(connection, block_id, node_pubkey): def write_block(connection, block): return connection.run( r.table('bigchain') - .insert(r.json(block), durability=WRITE_DURABILITY)) + .insert(r.json(block.to_str()), durability=WRITE_DURABILITY)) @register_query(RethinkDBConnection) diff --git a/bigchaindb/backend/rethinkdb/schema.py b/bigchaindb/backend/rethinkdb/schema.py index 66ed7b20..4a76a06b 100644 --- a/bigchaindb/backend/rethinkdb/schema.py +++ b/bigchaindb/backend/rethinkdb/schema.py @@ -60,7 +60,7 @@ def create_bigchain_secondary_index(connection, dbname): .table('bigchain') .index_create('transaction_id', r.row['block']['transactions']['id'], multi=True)) - # secondary index for asset uuid + # secondary index for asset links (in TRANSFER transactions) connection.run( r.db(dbname) .table('bigchain') diff --git a/bigchaindb/common/exceptions.py b/bigchaindb/common/exceptions.py index cec64706..c4ccd083 100644 --- a/bigchaindb/common/exceptions.py +++ b/bigchaindb/common/exceptions.py @@ -87,4 +87,4 @@ class AssetIdMismatch(Exception): class AmountError(Exception): - """Raised when the amount of a non-divisible asset is different then 1""" + """Raised when there is a problem with a transaction's output amounts""" diff --git a/bigchaindb/common/schema/transaction.yaml b/bigchaindb/common/schema/transaction.yaml index 610f386f..e856271d 100644 --- a/bigchaindb/common/schema/transaction.yaml +++ b/bigchaindb/common/schema/transaction.yaml @@ -104,25 +104,14 @@ definitions: description: | Description of the asset being transacted. In the case of a ``TRANSFER`` transaction, this field contains only the ID of asset. In the case - of a ``CREATE`` transaction, this field may contain properties: + of a ``CREATE`` transaction, this field contains only the user-defined + payload. additionalProperties: false - required: - - id properties: id: - "$ref": "#/definitions/uuid4" - divisible: - type: boolean + "$ref": "#/definitions/sha3_hexdigest" description: | - Whether or not the asset has a quantity that may be partially spent. - updatable: - type: boolean - description: | - Whether or not the description of the asset may be updated. Defaults to false. - refillable: - type: boolean - description: | - Whether the amount of the asset can change after its creation. Defaults to false. + ID of the transaction that created the asset. data: description: | User provided metadata associated with the asset. May also be ``null``. @@ -170,6 +159,10 @@ definitions: "$ref": "#/definitions/public_keys" description: | List of public keys associated with the conditions on an output. + amount: + type: integer + description: | + Integral amount of the asset represented by this condition. input: type: "object" description: diff --git a/bigchaindb/common/transaction.py b/bigchaindb/common/transaction.py index be688408..7810e688 100644 --- a/bigchaindb/common/transaction.py +++ b/bigchaindb/common/transaction.py @@ -1,6 +1,5 @@ from copy import deepcopy from functools import reduce -from uuid import uuid4 from cryptoconditions import (Fulfillment, ThresholdSha256Fulfillment, Ed25519Fulfillment) @@ -225,9 +224,12 @@ class Output(object): """ if not isinstance(public_keys, list) and public_keys is not None: raise TypeError('`public_keys` must be a list instance or None') + if not isinstance(amount, int): + raise TypeError('`amount` must be an int') + if amount < 1: + raise AmountError('`amount` must be greater than 0') self.fulfillment = fulfillment - # TODO: Not sure if we should validate for value here self.amount = amount self.public_keys = public_keys @@ -381,189 +383,6 @@ class Output(object): return cls(fulfillment, data['public_keys'], data['amount']) -class Asset(object): - """An Asset is a fungible unit to spend and lock with Transactions. - - Note: - Currently, the following flags are not yet fully supported: - - `divisible` - - `updatable` - - `refillable` - - Attributes: - data (dict): A dictionary of data that can be added to an Asset. - data_id (str): A unique identifier of `data`'s content. - divisible (bool): A flag indicating if an Asset can be divided. - updatable (bool): A flag indicating if an Asset can be updated. - refillable (bool): A flag indicating if an Asset can be refilled. - """ - - def __init__(self, data=None, data_id=None, divisible=False, - updatable=False, refillable=False): - """An Asset is not required to contain any extra data from outside.""" - self.data = data - self.data_id = data_id if data_id is not None else self.to_hash() - self.divisible = divisible - self.updatable = updatable - self.refillable = refillable - - self.validate_asset() - - def __eq__(self, other): - try: - other_dict = other.to_dict() - except AttributeError: - return False - return self.to_dict() == other_dict - - def to_dict(self): - """Transforms the object to a Python dictionary. - - Returns: - (dict): The Asset object as an alternative serialization - format. - """ - return { - 'id': self.data_id, - 'divisible': self.divisible, - 'updatable': self.updatable, - 'refillable': self.refillable, - 'data': self.data, - } - - @classmethod - def from_dict(cls, asset): - """Transforms a Python dictionary to an Asset object. - - Args: - asset (dict): The dictionary to be serialized. - - Returns: - :class:`~bigchaindb.common.transaction.Asset` - """ - return cls(asset.get('data'), asset['id'], - asset.get('divisible', False), - asset.get('updatable', False), - asset.get('refillable', False)) - - def to_hash(self): - """Generates a unqiue uuid for an Asset""" - return str(uuid4()) - - @staticmethod - def get_asset_id(transactions): - """Get the asset id from a list of transaction ids. - - This is useful when we want to check if the multiple inputs of a - transaction are related to the same asset id. - - Args: - transactions (:obj:`list` of :class:`~bigchaindb.common. - transaction.Transaction`): list of transaction usually inputs - that should have a matching asset_id - - Returns: - str: uuid of the asset. - - Raises: - AssetIdMismatch: If the inputs are related to different assets. - """ - - if not isinstance(transactions, list): - transactions = [transactions] - - # create a set of asset_ids - asset_ids = {tx.asset.data_id for tx in transactions} - - # check that all the transasctions have the same asset_id - if len(asset_ids) > 1: - raise AssetIdMismatch(('All inputs of all transactions passed' - ' need to have the same asset id')) - return asset_ids.pop() - - def validate_asset(self, amount=None): - """Validates the asset""" - if self.data is not None and not isinstance(self.data, dict): - raise TypeError('`data` must be a dict instance or None') - if not isinstance(self.divisible, bool): - raise TypeError('`divisible` must be a boolean') - if not isinstance(self.refillable, bool): - raise TypeError('`refillable` must be a boolean') - if not isinstance(self.updatable, bool): - raise TypeError('`updatable` must be a boolean') - - if self.refillable: - raise NotImplementedError('Refillable assets are not yet' - ' implemented') - if self.updatable: - raise NotImplementedError('Updatable assets are not yet' - ' implemented') - - # If the amount is supplied we can perform extra validations to - # the asset - if amount is not None: - if not isinstance(amount, int): - raise TypeError('`amount` must be an int') - - if self.divisible is False and amount != 1: - raise AmountError('non divisible assets always have' - ' amount equal to one') - - # Since refillable assets are not yet implemented this should - # raise and exception - if self.divisible is True and amount < 2: - raise AmountError('divisible assets must have an amount' - ' greater than one') - - -class AssetLink(Asset): - """An object for unidirectional linking to a Asset. - """ - - def __init__(self, data_id=None): - """Create an instance of a :class:`~.AssetLink`. - - Args: - data_id (str): A Asset to link to. - """ - self.data_id = data_id - - def __bool__(self): - return self.data_id is not None - - def __eq__(self, other): - return isinstance(other, AssetLink) and \ - self.to_dict() == other.to_dict() - - @classmethod - def from_dict(cls, link): - """Transforms a Python dictionary to a AssetLink object. - - Args: - link (dict): The link to be transformed. - - Returns: - :class:`~bigchaindb.common.transaction.AssetLink` - """ - try: - return cls(link['id']) - except TypeError: - return cls() - - def to_dict(self): - """Transforms the object to a Python dictionary. - - Returns: - (dict|None): The link as an alternative serialization format. - """ - if self.data_id is None: - return None - else: - return { - 'id': self.data_id - } - - class Transaction(object): """A Transaction is used to create and transfer assets. @@ -578,6 +397,10 @@ class Transaction(object): spend. outputs (:obj:`list` of :class:`~bigchaindb.common. transaction.Output`, optional): Define the assets to lock. + asset (dict): Asset payload for this Transaction. ``CREATE`` and + ``GENESIS`` Transactions require a dict with a ``data`` + property while ``TRANSFER`` Transactions require a dict with a + ``id`` property. metadata (dict): Metadata to be stored along with the Transaction. version (int): Defines the version number of a Transaction. @@ -598,28 +421,32 @@ class Transaction(object): Args: operation (str): Defines the operation of the Transaction. - asset (:class:`~bigchaindb.common.transaction.Asset`): An Asset - to be transferred or created in a Transaction. + asset (dict): Asset payload for this Transaction. inputs (:obj:`list` of :class:`~bigchaindb.common. transaction.Input`, optional): Define the assets to - spend. outputs (:obj:`list` of :class:`~bigchaindb.common. transaction.Output`, optional): Define the assets to lock. - metadata (dict): - Metadata to be stored along with the Transaction. + metadata (dict): Metadata to be stored along with the + Transaction. version (int): Defines the version number of a Transaction. - """ if operation not in Transaction.ALLOWED_OPERATIONS: allowed_ops = ', '.join(self.__class__.ALLOWED_OPERATIONS) raise ValueError('`operation` must be one of {}' .format(allowed_ops)) - # Only assets for 'CREATE' operations can be un-defined. - if (asset and not isinstance(asset, Asset) or - not asset and operation != Transaction.CREATE): - raise TypeError('`asset` must be an Asset instance') + # Asset payloads for 'CREATE' and 'GENESIS' operations must be None or + # dicts holding a `data` property. Asset payloads for 'TRANSFER' + # operations must be dicts holding an `id` property. + if (operation in [Transaction.CREATE, Transaction.GENESIS] and + asset is not None and not (isinstance(asset, dict) and 'data' in asset)): + raise TypeError(('`asset` must be None or a dict holding a `data` ' + " property instance for '{}' Transactions".format(operation))) + elif (operation == Transaction.TRANSFER and + not (isinstance(asset, dict) and 'id' in asset)): + raise TypeError(('`asset` must be a dict holding an `id` property ' + "for 'TRANSFER' Transactions".format(operation))) if outputs and not isinstance(outputs, list): raise TypeError('`outputs` must be a list instance or None') @@ -632,20 +459,11 @@ class Transaction(object): self.version = version if version is not None else self.VERSION self.operation = operation - self.asset = asset if asset else Asset() - self.outputs = outputs if outputs else [] - self.inputs = inputs if inputs else [] + self.asset = asset + self.inputs = inputs or [] + self.outputs = outputs or [] self.metadata = metadata - # validate asset - # we know that each transaction relates to a single asset - # we can sum the amount of all the outputs - # for transactions other then CREATE we only have an id so there is - # nothing we can validate - if self.operation == self.CREATE: - amount = sum([output.amount for output in self.outputs]) - self.asset.validate_asset(amount=amount) - @classmethod def create(cls, tx_signers, recipients, metadata=None, asset=None): """A simple way to generate a `CREATE` transaction. @@ -666,10 +484,10 @@ class Transaction(object): recipients (:obj:`list` of :obj:`str`): A list of keys that represent the recipients of the outputs of this Transaction. - metadata (dict): Python dictionary to be stored along with the + metadata (dict): The metadata to be stored along with the Transaction. - asset (:class:`~bigchaindb.common.transaction.Asset`): An Asset - to be created in this Transaction. + asset (dict): The metadata associated with the asset that will + be created in this Transaction. Returns: :class:`~bigchaindb.common.transaction.Transaction` @@ -682,6 +500,8 @@ class Transaction(object): raise ValueError('`tx_signers` list cannot be empty') if len(recipients) == 0: raise ValueError('`recipients` list cannot be empty') + if not (asset is None or isinstance(asset, dict)): + raise TypeError('`asset` must be a dict or None') inputs = [] outputs = [] @@ -698,10 +518,10 @@ class Transaction(object): # generate inputs inputs.append(Input.generate(tx_signers)) - return cls(cls.CREATE, asset, inputs, outputs, metadata) + return cls(cls.CREATE, {'data': asset}, inputs, outputs, metadata) @classmethod - def transfer(cls, inputs, recipients, asset, metadata=None): + def transfer(cls, inputs, recipients, asset_id, metadata=None): """A simple way to generate a `TRANSFER` transaction. Note: @@ -731,8 +551,8 @@ class Transaction(object): recipients (:obj:`list` of :obj:`str`): A list of ([keys],amount) that represent the recipients of this Transaction. - asset (:class:`~bigchaindb.common.transaction.Asset`): An Asset - to be transferred in this Transaction. + asset_id (str): The asset ID of the asset to be transferred in + this Transaction. metadata (dict): Python dictionary to be stored along with the Transaction. @@ -752,13 +572,15 @@ class Transaction(object): for recipient in recipients: if not isinstance(recipient, tuple) or len(recipient) != 2: raise ValueError(('Each `recipient` in the list must be a' - ' tuple of `([],' ' )`')) pub_keys, amount = recipient outputs.append(Output.generate(pub_keys, amount)) + if not isinstance(asset_id, str): + raise TypeError('`asset_id` must be a string') + inputs = deepcopy(inputs) - return cls(cls.TRANSFER, asset, inputs, outputs, metadata) + return cls(cls.TRANSFER, {'id': asset_id}, inputs, outputs, metadata) def __eq__(self, other): try: @@ -1092,18 +914,12 @@ class Transaction(object): Returns: dict: The Transaction as an alternative serialization format. """ - if self.operation in (self.__class__.GENESIS, self.__class__.CREATE): - asset = self.asset.to_dict() - else: - # NOTE: An `asset` in a `TRANSFER` only contains the asset's id - asset = {'id': self.asset.data_id} - tx = { 'inputs': [input_.to_dict() for input_ in self.inputs], 'outputs': [output.to_dict() for output in self.outputs], 'operation': str(self.operation), 'metadata': self.metadata, - 'asset': asset, + 'asset': self.asset, 'version': self.version, } @@ -1157,6 +973,40 @@ class Transaction(object): tx = Transaction._remove_signatures(self.to_dict()) return Transaction._to_str(tx) + @staticmethod + def get_asset_id(transactions): + """Get the asset id from a list of :class:`~.Transactions`. + + This is useful when we want to check if the multiple inputs of a + transaction are related to the same asset id. + + Args: + transactions (:obj:`list` of :class:`~bigchaindb.common. + transaction.Transaction`): A list of Transactions. + Usually input Transactions that should have a matching + asset ID. + + Returns: + str: ID of the asset. + + Raises: + :exc:`AssetIdMismatch`: If the inputs are related to different + assets. + """ + + if not isinstance(transactions, list): + transactions = [transactions] + + # create a set of the transactions' asset ids + asset_ids = {tx.id if tx.operation == Transaction.CREATE else tx.asset['id'] + for tx in transactions} + + # check that all the transasctions have the same asset id + if len(asset_ids) > 1: + raise AssetIdMismatch(('All inputs of all transactions passed' + ' need to have the same asset id')) + return asset_ids.pop() + @staticmethod def validate_structure(tx_body): """Validate the transaction ID of a transaction @@ -1193,10 +1043,5 @@ class Transaction(object): cls.validate_structure(tx) inputs = [Input.from_dict(input_) for input_ in tx['inputs']] outputs = [Output.from_dict(output) for output in tx['outputs']] - if tx['operation'] in [cls.CREATE, cls.GENESIS]: - asset = Asset.from_dict(tx['asset']) - else: - asset = AssetLink.from_dict(tx['asset']) - - return cls(tx['operation'], asset, inputs, outputs, + return cls(tx['operation'], tx['asset'], inputs, outputs, tx['metadata'], tx['version']) diff --git a/bigchaindb/core.py b/bigchaindb/core.py index cd0846b2..a69780bb 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -6,7 +6,7 @@ from time import time from itertools import compress from bigchaindb.common import crypto, exceptions from bigchaindb.common.util import gen_timestamp, serialize -from bigchaindb.common.transaction import TransactionLink, Asset +from bigchaindb.common.transaction import TransactionLink import bigchaindb @@ -348,13 +348,12 @@ class Bigchain(object): asset_id (str): The asset id. Returns: - :class:`~bigchaindb.common.transaction.Asset` if the asset - exists else None. + dict if the asset exists else None. """ cursor = backend.query.get_asset_by_id(self.connection, asset_id) cursor = list(cursor) if cursor: - return Asset.from_dict(cursor[0]['asset']) + return cursor[0]['asset'] def get_spent(self, txid, output): """Check if a `txid` was already used as an input. @@ -517,7 +516,7 @@ class Bigchain(object): block (Block): block to write to bigchain. """ - return backend.query.write_block(self.connection, block.to_str()) + return backend.query.write_block(self.connection, block) def transaction_exists(self, transaction_id): return backend.query.has_transaction(self.connection, transaction_id) diff --git a/bigchaindb/models.py b/bigchaindb/models.py index a5c43f9c..69b950e5 100644 --- a/bigchaindb/models.py +++ b/bigchaindb/models.py @@ -4,7 +4,7 @@ from bigchaindb.common.exceptions import (InvalidHash, InvalidSignature, TransactionDoesNotExist, TransactionNotInValidBlock, AssetIdMismatch, AmountError) -from bigchaindb.common.transaction import Transaction, Asset +from bigchaindb.common.transaction import Transaction from bigchaindb.common.util import gen_timestamp, serialize from bigchaindb.common.schema import validate_transaction_schema @@ -39,21 +39,30 @@ class Transaction(Transaction): input_conditions = [] inputs_defined = all([input_.fulfills for input_ in self.inputs]) + # validate amounts + if any(output.amount < 1 for output in self.outputs): + raise AmountError('`amount` needs to be greater than zero') + if self.operation in (Transaction.CREATE, Transaction.GENESIS): + # validate asset + if self.asset['data'] is not None and not isinstance(self.asset['data'], dict): + raise TypeError(('`asset.data` must be a dict instance or ' + 'None for `CREATE` transactions')) # validate inputs if inputs_defined: raise ValueError('A CREATE operation has no inputs') - # validate asset - amount = sum([output.amount for output in self.outputs]) - self.asset.validate_asset(amount=amount) elif self.operation == Transaction.TRANSFER: + # validate asset + if not isinstance(self.asset['id'], str): + raise ValueError(('`asset.id` must be a string for ' + '`TRANSFER` transations')) + # check inputs if not inputs_defined: raise ValueError('Only `CREATE` transactions can have null ' 'inputs') - # check inputs + # store the inputs so that we can check if the asset ids match input_txs = [] - input_amount = 0 for input_ in self.inputs: input_txid = input_.fulfills.txid input_tx, status = bigchain.\ @@ -78,24 +87,21 @@ class Transaction(Transaction): input_txs.append(input_tx) if output.amount < 1: raise AmountError('`amount` needs to be greater than zero') - input_amount += output.amount # validate asset id - asset_id = Asset.get_asset_id(input_txs) - if asset_id != self.asset.data_id: - raise AssetIdMismatch('The asset id of the input does not ' - 'match the asset id of the transaction') + asset_id = Transaction.get_asset_id(input_txs) + if asset_id != self.asset['id']: + raise AssetIdMismatch(('The asset id of the input does not' + ' match the asset id of the' + ' transaction')) - # get the asset creation to see if its divisible or not - asset = bigchain.get_asset_by_id(asset_id) - # validate the asset - asset.validate_asset(amount=input_amount) # validate the amounts - output_amount = 0 for output in self.outputs: if output.amount < 1: raise AmountError('`amount` needs to be greater than zero') - output_amount += output.amount + + input_amount = sum([input_condition.amount for input_condition in input_conditions]) + output_amount = sum([output_condition.amount for output_condition in self.outputs]) if output_amount != input_amount: raise AmountError(('The amount used in the inputs `{}`' @@ -110,8 +116,8 @@ class Transaction(Transaction): if not self.inputs_valid(input_conditions): raise InvalidSignature() - else: - return self + + return self @classmethod def from_dict(cls, tx_body): diff --git a/docs/server/generate_http_server_api_documentation.py b/docs/server/generate_http_server_api_documentation.py index 80b47cf2..7c7c4b97 100644 --- a/docs/server/generate_http_server_api_documentation.py +++ b/docs/server/generate_http_server_api_documentation.py @@ -4,7 +4,7 @@ import json import os import os.path -from bigchaindb.common.transaction import Asset, Transaction +from bigchaindb.common.transaction import Transaction TPLS = {} @@ -62,8 +62,7 @@ def main(): """ Main function """ privkey = 'CfdqtD7sS7FgkMoGPXw55MVGGFwQLAoHYTcBhZDtF99Z' pubkey = '4K9sWUMFwTgaDGPfdynrbxWqWS6sWmKbZoTjxLtVUibD' - asset = Asset(None, 'e6969f87-4fc9-4467-b62a-f0dfa1c85002') - tx = Transaction.create([pubkey], [([pubkey], 1)], asset=asset) + tx = Transaction.create([pubkey], [([pubkey], 1)]) tx = tx.sign([privkey]) tx_json = json.dumps(tx.to_dict(), indent=2, sort_keys=True) diff --git a/docs/server/source/cloud-deployment-starter-templates/azure-quickstart-template.md b/docs/server/source/cloud-deployment-starter-templates/azure-quickstart-template.md index 577d96a6..9f8a8d6e 100644 --- a/docs/server/source/cloud-deployment-starter-templates/azure-quickstart-template.md +++ b/docs/server/source/cloud-deployment-starter-templates/azure-quickstart-template.md @@ -22,8 +22,6 @@ One can deploy a BigchainDB node on Azure using the template in the `bigchaindb- 1. You should be prompted for a password. Give the `` you entered into the form. -1. If pull request [#2884](https://github.com/Azure/azure-quickstart-templates/pull/2884) has been merged, then skip this step. Otherwise, go to the `init.sh` script at [https://github.com/Azure/azure-quickstart-templates/blob/master/bigchaindb-on-ubuntu/scripts/init.sh](https://github.com/Azure/azure-quickstart-templates/blob/master/bigchaindb-on-ubuntu/scripts/init.sh). Your goal is to read through that script and check if each line should be run manually in the terminal. For example, is RethinkDB already installed and running? You can figure that out using `pgrep rethinkdb`. And so on. If in doubt, just try running each command in `init.sh`. - 1. Configure BigchainDB Server by doing: ```text bigchaindb configure diff --git a/docs/server/source/data-models/asset-model.md b/docs/server/source/data-models/asset-model.md index 5a2ed748..312c6765 100644 --- a/docs/server/source/data-models/asset-model.md +++ b/docs/server/source/data-models/asset-model.md @@ -5,27 +5,17 @@ To avoid redundant data in transactions, the digital asset model is different fo A digital asset's properties are defined in a `CREATE` transaction with the following model: ```json { - "id": "", - "divisible": "", - "updatable": "", - "refillable": "", "data": "" } ``` -For `TRANSFER` transactions we only keep the asset id. +For `TRANSFER` transactions we only keep the asset ID: ```json { - "id": "" + "id": "" } ``` -- `id`: UUID version 4 (random) converted to a string of hex digits in standard form. Added server side. -- `divisible`: Whether the asset is divisible or not. Defaults to false. -- `updatable`: Whether the data in the asset can be updated in the future or not. Defaults to false. -- `refillable`: Whether the amount of the asset can change after its creation. Defaults to false. +- `id`: The ID of the `CREATE` transaction that created the asset. - `data`: A user supplied JSON document with custom information about the asset. Defaults to null. -- _amount_: The amount of "shares". Only relevant if the asset is marked as divisible. Defaults to 1. The amount is not specified in the asset, but in the outputs (see next section). - -At the time of this writing, updatable and refillable assets are not yet implemented. diff --git a/docs/server/source/dev-and-test/index.rst b/docs/server/source/dev-and-test/index.rst index 4abc56de..f79c20a5 100644 --- a/docs/server/source/dev-and-test/index.rst +++ b/docs/server/source/dev-and-test/index.rst @@ -5,4 +5,4 @@ Develop & Test BigchainDB Server :maxdepth: 1 setup-run-node - running-unit-tests + running-all-tests diff --git a/docs/server/source/dev-and-test/running-all-tests.md b/docs/server/source/dev-and-test/running-all-tests.md new file mode 100644 index 00000000..bc9d32a7 --- /dev/null +++ b/docs/server/source/dev-and-test/running-all-tests.md @@ -0,0 +1,3 @@ +# Running All Tests + +All documentation about writing and running tests (unit and integration tests) was moved to [the file `bigchaindb/tests/README.md`](https://github.com/bigchaindb/bigchaindb/blob/master/tests/README.md). \ No newline at end of file diff --git a/docs/server/source/dev-and-test/running-unit-tests.md b/docs/server/source/dev-and-test/running-unit-tests.md deleted file mode 100644 index c3347d79..00000000 --- a/docs/server/source/dev-and-test/running-unit-tests.md +++ /dev/null @@ -1,36 +0,0 @@ -# Running Unit Tests - -Once you've installed BigchainDB Server, you may want to run all the unit tests. This section explains how. - -First of all, if you installed BigchainDB Server using `pip` (i.e. by getting the package from PyPI), then you didn't install the tests. **Before you can run all the unit tests, you must [install BigchainDB from source](setup-run-node.html#how-to-install-bigchaindb-from-source).** - -To run all the unit tests, first make sure you have RethinkDB running: - -```text -$ rethinkdb -``` - -then in another terminal, do: - -```text -$ python setup.py test -``` - -(Aside: How does the above command work? The documentation for [pytest-runner](https://pypi.python.org/pypi/pytest-runner) explains. We use [pytest](http://docs.pytest.org/en/latest/) to write all unit tests.) - - -### Using docker-compose to Run the Tests - -You can also use `docker-compose` to run the unit tests. - -Start `RethinkDB` in the background: - -```text -$ docker-compose up -d rdb -``` - -then run the unit tests using: - -```text -$ docker-compose run --rm bdb py.test -v -``` diff --git a/docs/server/source/dev-and-test/setup-run-node.md b/docs/server/source/dev-and-test/setup-run-node.md index db188f71..0cf5334c 100644 --- a/docs/server/source/dev-and-test/setup-run-node.md +++ b/docs/server/source/dev-and-test/setup-run-node.md @@ -50,6 +50,9 @@ Build the images: docker-compose build ``` +**Note**: If you're upgrading BigchainDB and have previously already built the images, you may need +to rebuild them after the upgrade to install any new dependencies. + Start RethinkDB: ```bash diff --git a/docs/server/source/drivers-clients/http-client-server-api.rst b/docs/server/source/drivers-clients/http-client-server-api.rst index 031fef7b..969d912b 100644 --- a/docs/server/source/drivers-clients/http-client-server-api.rst +++ b/docs/server/source/drivers-clients/http-client-server-api.rst @@ -5,31 +5,23 @@ The HTTP Client-Server API The HTTP client-server API is currently quite rudimentary. For example, there is no ability to do complex queries using the HTTP API. We plan to add - querying capabilities in the future. + more querying capabilities in the future. -When you start Bigchaindb using `bigchaindb start`, an HTTP API is exposed at -the address stored in the BigchainDB node configuration settings. The default -is: +This page assumes you already know an API Root URL +for a BigchainDB node or reverse proxy. +It should be something like ``http://apihosting4u.net:9984`` +or ``http://12.34.56.78:9984``. -`http://localhost:9984/api/v1/ `_ - -but that address can be changed by changing the "API endpoint" configuration -setting (e.g. in a local config file). There's more information about setting -the API endpoint in :doc:`the section about BigchainDB Configuration Settings -<../server-reference/configuration>`. - -There are other configuration settings related to the web server (serving the -HTTP API). In particular, the default is for the web server socket to bind to -``localhost:9984`` but that can be changed (e.g. to ``0.0.0.0:9984``). For more -details, see the "server" settings ("bind", "workers" and "threads") in -:doc:`the section about BigchainDB Configuration Settings -<../server-reference/configuration>`. +If you set up a BigchainDB node or reverse proxy yourself, +and you're not sure what the API Root URL is, +then see the last section of this page for help. API Root URL ------------ -If you send an HTTP GET request to e.g. ``http://localhost:9984`` +If you send an HTTP GET request to the API Root URL +e.g. ``http://localhost:9984`` or ``http://apihosting4u.net:9984`` (with no ``/api/v1/`` on the end), then you should get an HTTP response @@ -47,25 +39,6 @@ with something like the following in the body: "version": "0.6.0" } -If the API endpoint is publicly-accessible, -then the public API Root URL is determined as follows: - -- The public IP address (like 12.34.56.78) - is the public IP address of the machine exposing - the HTTP API to the public internet (e.g. either the machine hosting - Gunicorn or the machine running the reverse proxy such as Nginx). - It's determined by AWS, Azure, Rackspace, or whoever is hosting the machine. - -- The DNS hostname (like apihosting4u.net) is determined by DNS records, - such as an "A Record" associating apihosting4u.net with 12.34.56.78 - -- The port (like 9984) is determined by the ``server.bind`` setting - if Gunicorn is exposed directly to the public Internet. - If a reverse proxy (like Nginx) is exposed directly to the public Internet - instead, then it could expose the HTTP API on whatever port it wants to. - (It should expose the HTTP API on port 9984, but it's not bound to do - that by anything other than convention.) - POST /transactions/ ------------------- @@ -195,3 +168,40 @@ GET /unspents/ :statuscode 200: A list of outputs were found and returned in the body of the response. :statuscode 400: The request wasn't understood by the server, e.g. the ``owner_after`` querystring was not included in the request. + + +Determining the API Root URL +---------------------------- + +When you start BigchainDB Server using ``bigchaindb start``, +an HTTP API is exposed at some address. The default is: + +`http://localhost:9984/api/v1/ `_ + +It's bound to ``localhost``, +so you can access it from the same machine, +but it won't be directly accessible from the outside world. +(The outside world could connect via a SOCKS proxy or whatnot.) + +The documentation about BigchainDB Server :any:`Configuration Settings` +has a section about how to set ``server.bind`` so as to make +the HTTP API publicly accessible. + +If the API endpoint is publicly accessible, +then the public API Root URL is determined as follows: + +- The public IP address (like 12.34.56.78) + is the public IP address of the machine exposing + the HTTP API to the public internet (e.g. either the machine hosting + Gunicorn or the machine running the reverse proxy such as Nginx). + It's determined by AWS, Azure, Rackspace, or whoever is hosting the machine. + +- The DNS hostname (like apihosting4u.net) is determined by DNS records, + such as an "A Record" associating apihosting4u.net with 12.34.56.78 + +- The port (like 9984) is determined by the ``server.bind`` setting + if Gunicorn is exposed directly to the public Internet. + If a reverse proxy (like Nginx) is exposed directly to the public Internet + instead, then it could expose the HTTP API on whatever port it wants to. + (It should expose the HTTP API on port 9984, but it's not bound to do + that by anything other than convention.) diff --git a/tests/README.md b/tests/README.md index 8e66a708..e84516a1 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,18 +1,99 @@ -# Tests +# BigchainDB Server Tests -## Test Structure +## The tests/ Folder -Generally all tests are meant to be unit tests, with the exception of those in the [`integration/` folder](./integration/). +The `tests/` folder is where all the tests for BigchainDB Server live. Most of them are unit tests. Integration tests are in the [`tests/integration/` folder](./integration/). A few notes: -- [`common/`](./common/) contains self-contained tests only testing +- [`tests/common/`](./common/) contains self-contained tests only testing [`bigchaindb/common/`](../bigchaindb/common/) -- [`db/`](./db/) contains tests requiring the database backend (e.g. RethinkDB) +- [`tests/db/`](./db/) contains tests requiring the database backend (e.g. RethinkDB) -## Pytest Customizations -Customizations we've added to `pytest`: +## Writing Tests -- `--database-backend`: Defines the backend to use for the tests. Must be one of the backends - available in the [server configuration](https://docs.bigchaindb.com/projects/server/en/latest/server-reference/configuration.html) +We write unit and integration tests for our Python code using the [pytest](http://pytest.org/latest/) framework. You can use the tests in the `tests/` folder as templates or examples. + + +## Running Tests + +### Running Tests Directly + +If you installed BigchainDB Server using `pip install bigchaindb`, then you didn't install the tests. Before you can run all the tests, you must install BigchainDB from source. The [`CONTRIBUTING.md` file](../CONTRIBUTING.md) has instructions for how to do that. + +Next, make sure you have RethinkDB running in the background (e.g. using `rethinkdb --daemon`). + +Now you can run all tests using: +```text +py.test -v +``` + +or, if that doesn't work, try: +```text +python -m pytest -v +``` + +or: +```text +python setup.py test +``` + +How does `python setup.py test` work? The documentation for [pytest-runner](https://pypi.python.org/pypi/pytest-runner) explains. + +The `pytest` command has many options. If you want to learn about all the things you can do with pytest, see [the pytest documentation](http://pytest.org/latest/). We've also added a customization to pytest: + +`--database-backend`: Defines the backend to use for the tests. +It must be one of the backends available in the [server configuration](https://docs.bigchaindb.com/projects/server/en/latest/server-reference/configuration.html). + + +### Running Tests with Docker Compose + +You can also use [Docker Compose](https://docs.docker.com/compose/) to run all the tests. + +First, start `RethinkDB` in the background: + +```text +$ docker-compose up -d rdb +``` + +then run the tests using: + +```text +$ docker-compose run --rm bdb py.test -v +``` + +If you've upgraded to a newer version of BigchainDB, you might have to rebuild the images before +being able to run the tests. Run: + +```text +$ docker-compose build +``` + +to rebuild all the images (usually you only need to rebuild the `bdb` image). + +## Automated Testing of All Pull Requests + +We use [Travis CI](https://travis-ci.com/), so that whenever someone creates a new BigchainDB pull request on GitHub, Travis CI gets the new code and does _a bunch of stuff_. You can find out what we tell Travis CI to do in [the `.travis.yml` file](.travis.yml): it tells Travis CI how to install BigchainDB, how to run all the tests, and what to do "after success" (e.g. run `codecov`). (We use [Codecov](https://codecov.io/) to get a rough estimate of our test coverage.) + + +### Tox + +We use [tox](https://tox.readthedocs.io/en/latest/) to run multiple suites of tests against multiple environments during automated testing. Generally you don't need to run this yourself, but it might be useful when troubleshooting a failing Travis CI build. + +To run all the tox tests, use: +```text +tox +``` + +or: +```text +python -m tox +``` + +To run only a few environments, use the `-e` flag: +```text +tox -e {ENVLIST} +``` + +where `{ENVLIST}` is one or more of the environments specified in the [tox.ini file](../tox.ini). diff --git a/tests/assets/test_digital_assets.py b/tests/assets/test_digital_assets.py index 4a918cb8..160d93d2 100644 --- a/tests/assets/test_digital_assets.py +++ b/tests/assets/test_digital_assets.py @@ -1,7 +1,8 @@ import pytest -from unittest.mock import patch +import random +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_asset_transfer(b, user_pk, user_sk): from bigchaindb.models import Transaction @@ -10,49 +11,26 @@ def test_asset_transfer(b, user_pk, user_sk): tx_create = b.get_transaction(tx_input.txid) tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_pk], 1)], - tx_create.asset) + tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) assert tx_transfer_signed.validate(b) == tx_transfer_signed - assert tx_transfer_signed.asset.data_id == tx_create.asset.data_id + assert tx_transfer_signed.asset['id'] == tx_create.id def test_validate_bad_asset_creation(b, user_pk): - from bigchaindb.models import Transaction, Asset - - # `divisible` needs to be a boolean - tx = Transaction.create([b.me], [([user_pk], 1)]) - tx.asset.divisible = 1 - with patch.object(Asset, 'validate_asset', return_value=None): - tx_signed = tx.sign([b.me_private]) - with pytest.raises(TypeError): - tx_signed.validate(b) - - # `refillable` needs to be a boolean - tx = Transaction.create([b.me], [([user_pk], 1)]) - tx.asset.refillable = 1 - with patch.object(Asset, 'validate_asset', return_value=None): - tx_signed = tx.sign([b.me_private]) - with pytest.raises(TypeError): - b.validate_transaction(tx_signed) - - # `updatable` needs to be a boolean - tx = Transaction.create([b.me], [([user_pk], 1)]) - tx.asset.updatable = 1 - with patch.object(Asset, 'validate_asset', return_value=None): - tx_signed = tx.sign([b.me_private]) - with pytest.raises(TypeError): - b.validate_transaction(tx_signed) + from bigchaindb.models import Transaction # `data` needs to be a dictionary tx = Transaction.create([b.me], [([user_pk], 1)]) - tx.asset.data = 'a' - with patch.object(Asset, 'validate_asset', return_value=None): - tx_signed = tx.sign([b.me_private]) + tx.asset['data'] = 'a' + tx_signed = tx.sign([b.me_private]) + with pytest.raises(TypeError): b.validate_transaction(tx_signed) +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_validate_transfer_asset_id_mismatch(b, user_pk, user_sk): from bigchaindb.common.exceptions import AssetIdMismatch @@ -61,31 +39,32 @@ def test_validate_transfer_asset_id_mismatch(b, user_pk, user_sk): tx_create = b.get_owned_ids(user_pk).pop() tx_create = b.get_transaction(tx_create.txid) tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_pk], 1)], - tx_create.asset) - tx_transfer.asset.data_id = 'aaa' + tx_create.id) + tx_transfer.asset['id'] = 'aaa' tx_transfer_signed = tx_transfer.sign([user_sk]) with pytest.raises(AssetIdMismatch): tx_transfer_signed.validate(b) def test_get_asset_id_create_transaction(b, user_pk): - from bigchaindb.models import Transaction, Asset + from bigchaindb.models import Transaction tx_create = Transaction.create([b.me], [([user_pk], 1)]) - asset_id = Asset.get_asset_id(tx_create) + asset_id = Transaction.get_asset_id(tx_create) - assert asset_id == tx_create.asset.data_id + assert asset_id == tx_create.id +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_get_asset_id_transfer_transaction(b, user_pk, user_sk): - from bigchaindb.models import Transaction, Asset + 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.asset) + tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) # create a block block = b.create_block([tx_transfer_signed]) @@ -93,38 +72,41 @@ def test_get_asset_id_transfer_transaction(b, user_pk, user_sk): # vote the block valid vote = b.vote(block.id, b.get_last_voted_block().id, True) b.write_vote(vote) - asset_id = Asset.get_asset_id(tx_transfer) + asset_id = Transaction.get_asset_id(tx_transfer) - assert asset_id == tx_transfer.asset.data_id + assert asset_id == tx_transfer.asset['id'] def test_asset_id_mismatch(b, user_pk): - from bigchaindb.models import Transaction, Asset + from bigchaindb.models import Transaction from bigchaindb.common.exceptions import AssetIdMismatch - tx1 = Transaction.create([b.me], [([user_pk], 1)]) - tx2 = Transaction.create([b.me], [([user_pk], 1)]) + tx1 = Transaction.create([b.me], [([user_pk], 1)], + metadata={'msg': random.random()}) + tx2 = Transaction.create([b.me], [([user_pk], 1)], + metadata={'msg': random.random()}) with pytest.raises(AssetIdMismatch): - Asset.get_asset_id([tx1, tx2]) + 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.asset.data_id + 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].asset.data_id == asset_id + assert txs[0].id == asset_id # create a transfer transaction tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_pk], 1)], - tx_create.asset) + tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) # create the block block = b.create_block([tx_transfer_signed]) @@ -138,26 +120,28 @@ def test_get_transactions_by_asset_id(b, user_pk, user_sk): assert len(txs) == 2 assert tx_create.id in [t.id for t in txs] assert tx_transfer.id in [t.id for t in txs] - assert asset_id == txs[0].asset.data_id - assert asset_id == txs[1].asset.data_id + # FIXME: can I rely on the ordering here? + assert asset_id == txs[0].id + assert asset_id == txs[1].asset['id'] +@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.asset.data_id + 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].asset.data_id == asset_id + assert txs[0].id == asset_id # create a transfer transaction tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_pk], 1)], - tx_create.asset) + tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) # create the block block = b.create_block([tx_transfer_signed]) @@ -171,17 +155,17 @@ def test_get_transactions_by_asset_id_with_invalid_block(b, user_pk, user_sk): 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) - asset_id = tx_create.asset.data_id # create a transfer transaction tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_pk], 1)], - tx_create.asset) + tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) # create the block block = b.create_block([tx_transfer_signed]) @@ -190,6 +174,7 @@ def test_get_asset_by_id(b, user_pk, user_sk): 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 @@ -198,44 +183,21 @@ def test_get_asset_by_id(b, user_pk, user_sk): def test_create_invalid_divisible_asset(b, user_pk, user_sk): - from bigchaindb.models import Transaction, Asset + from bigchaindb.models import Transaction from bigchaindb.common.exceptions import AmountError - # non divisible assets cannot have amount > 1 - # Transaction.__init__ should raise an exception - asset = Asset(divisible=False) - with pytest.raises(AmountError): - Transaction.create([user_pk], [([user_pk], 2)], asset=asset) + # Asset amount must be more than 0 + tx = Transaction.create([user_pk], [([user_pk], 1)]) + tx.outputs[0].amount = 0 + tx.sign([user_sk]) - # divisible assets need to have an amount > 1 - # Transaction.__init__ should raise an exception - asset = Asset(divisible=True) with pytest.raises(AmountError): - Transaction.create([user_pk], [([user_pk], 1)], asset=asset) - - # even if a transaction is badly constructed the server should raise the - # exception - asset = Asset(divisible=False) - with patch.object(Asset, 'validate_asset', return_value=None): - tx = Transaction.create([user_pk], [([user_pk], 2)], asset=asset) - tx_signed = tx.sign([user_sk]) - with pytest.raises(AmountError): - tx_signed.validate(b) - assert b.is_valid_transaction(tx_signed) is False - - asset = Asset(divisible=True) - with patch.object(Asset, 'validate_asset', return_value=None): - tx = Transaction.create([user_pk], [([user_pk], 1)], asset=asset) - tx_signed = tx.sign([user_sk]) - with pytest.raises(AmountError): - tx_signed.validate(b) - assert b.is_valid_transaction(tx_signed) is False + b.validate_transaction(tx) def test_create_valid_divisible_asset(b, user_pk, user_sk): - from bigchaindb.models import Transaction, Asset + from bigchaindb.models import Transaction - asset = Asset(divisible=True) - tx = Transaction.create([user_pk], [([user_pk], 2)], asset=asset) + tx = Transaction.create([user_pk], [([user_pk], 2)]) tx_signed = tx.sign([user_sk]) assert b.is_valid_transaction(tx_signed) diff --git a/tests/assets/test_divisible_assets.py b/tests/assets/test_divisible_assets.py index 9716ec5b..31e7890f 100644 --- a/tests/assets/test_divisible_assets.py +++ b/tests/assets/test_divisible_assets.py @@ -1,7 +1,5 @@ import pytest -from unittest.mock import patch - # CREATE divisible asset # Single input @@ -10,10 +8,8 @@ from unittest.mock import patch # Single owners_after def test_single_in_single_own_single_out_single_own_create(b, user_pk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset - asset = Asset(divisible=True) - tx = Transaction.create([b.me], [([user_pk], 100)], asset=asset) + tx = Transaction.create([b.me], [([user_pk], 100)]) tx_signed = tx.sign([b.me_private]) assert tx_signed.validate(b) == tx_signed @@ -29,11 +25,8 @@ def test_single_in_single_own_single_out_single_own_create(b, user_pk): # Single owners_after per output def test_single_in_single_own_multiple_out_single_own_create(b, user_pk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset - asset = Asset(divisible=True) - tx = Transaction.create([b.me], [([user_pk], 50), ([user_pk], 50)], - asset=asset) + tx = Transaction.create([b.me], [([user_pk], 50), ([user_pk], 50)]) tx_signed = tx.sign([b.me_private]) assert tx_signed.validate(b) == tx_signed @@ -50,10 +43,8 @@ def test_single_in_single_own_multiple_out_single_own_create(b, user_pk): # Multiple owners_after def test_single_in_single_own_single_out_multiple_own_create(b, user_pk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset - asset = Asset(divisible=True) - tx = Transaction.create([b.me], [([user_pk, user_pk], 100)], asset=asset) + tx = Transaction.create([b.me], [([user_pk, user_pk], 100)]) tx_signed = tx.sign([b.me_private]) assert tx_signed.validate(b) == tx_signed @@ -75,12 +66,8 @@ def test_single_in_single_own_single_out_multiple_own_create(b, user_pk): # owners_after def test_single_in_single_own_multiple_out_mix_own_create(b, user_pk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset - asset = Asset(divisible=True) - tx = Transaction.create([b.me], - [([user_pk], 50), ([user_pk, user_pk], 50)], - asset=asset) + tx = Transaction.create([b.me], [([user_pk], 50), ([user_pk, user_pk], 50)]) tx_signed = tx.sign([b.me_private]) assert tx_signed.validate(b) == tx_signed @@ -102,10 +89,8 @@ def test_single_in_single_own_multiple_out_mix_own_create(b, user_pk): def test_single_in_multiple_own_single_out_single_own_create(b, user_pk, user_sk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset - asset = Asset(divisible=True) - tx = Transaction.create([b.me, user_pk], [([user_pk], 100)], asset=asset) + tx = Transaction.create([b.me, user_pk], [([user_pk], 100)]) tx_signed = tx.sign([b.me_private, user_sk]) assert tx_signed.validate(b) == tx_signed assert len(tx_signed.outputs) == 1 @@ -126,15 +111,14 @@ def test_single_in_multiple_own_single_out_single_own_create(b, user_pk, # else there will be no genesis block and b.get_last_voted_block will # fail. # Is there a better way of doing this? +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_single_in_single_own_single_out_single_own_transfer(b, user_pk, user_sk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset # CREATE divisible asset - asset = Asset(divisible=True) - tx_create = Transaction.create([b.me], [([user_pk], 100)], asset=asset) + tx_create = Transaction.create([b.me], [([user_pk], 100)]) tx_create_signed = tx_create.sign([b.me_private]) # create block block = b.create_block([tx_create_signed]) @@ -146,7 +130,7 @@ def test_single_in_single_own_single_out_single_own_transfer(b, user_pk, # TRANSFER tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)], - asset=tx_create.asset) + asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) assert tx_transfer_signed.validate(b) @@ -160,15 +144,14 @@ def test_single_in_single_own_single_out_single_own_transfer(b, user_pk, # Single owners_before # Multiple output # Single owners_after +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_single_in_single_own_multiple_out_single_own_transfer(b, user_pk, user_sk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset # CREATE divisible asset - asset = Asset(divisible=True) - tx_create = Transaction.create([b.me], [([user_pk], 100)], asset=asset) + tx_create = Transaction.create([b.me], [([user_pk], 100)]) tx_create_signed = tx_create.sign([b.me_private]) # create block block = b.create_block([tx_create_signed]) @@ -181,7 +164,7 @@ def test_single_in_single_own_multiple_out_single_own_transfer(b, user_pk, # TRANSFER tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 50), ([b.me], 50)], - asset=tx_create.asset) + asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) assert tx_transfer_signed.validate(b) == tx_transfer_signed @@ -196,15 +179,14 @@ def test_single_in_single_own_multiple_out_single_own_transfer(b, user_pk, # Single owners_before # Single output # Multiple owners_after +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_single_in_single_own_single_out_multiple_own_transfer(b, user_pk, user_sk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset # CREATE divisible asset - asset = Asset(divisible=True) - tx_create = Transaction.create([b.me], [([user_pk], 100)], asset=asset) + tx_create = Transaction.create([b.me], [([user_pk], 100)]) tx_create_signed = tx_create.sign([b.me_private]) # create block block = b.create_block([tx_create_signed]) @@ -217,7 +199,7 @@ def test_single_in_single_own_single_out_multiple_own_transfer(b, user_pk, # TRANSFER tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me, b.me], 100)], - asset=tx_create.asset) + asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) assert tx_transfer_signed.validate(b) == tx_transfer_signed @@ -237,15 +219,14 @@ def test_single_in_single_own_single_out_multiple_own_transfer(b, user_pk, # Multiple outputs # Mix: one output with a single owners_after, one output with multiple # owners_after +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_single_in_single_own_multiple_out_mix_own_transfer(b, user_pk, user_sk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset # CREATE divisible asset - asset = Asset(divisible=True) - tx_create = Transaction.create([b.me], [([user_pk], 100)], asset=asset) + tx_create = Transaction.create([b.me], [([user_pk], 100)]) tx_create_signed = tx_create.sign([b.me_private]) # create block block = b.create_block([tx_create_signed]) @@ -258,7 +239,7 @@ def test_single_in_single_own_multiple_out_mix_own_transfer(b, user_pk, # TRANSFER tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 50), ([b.me, b.me], 50)], - asset=tx_create.asset) + asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) assert tx_transfer_signed.validate(b) == tx_transfer_signed @@ -278,16 +259,14 @@ def test_single_in_single_own_multiple_out_mix_own_transfer(b, user_pk, # Multiple owners_before # Single output # Single owners_after +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_single_in_multiple_own_single_out_single_own_transfer(b, user_pk, user_sk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset # CREATE divisible asset - asset = Asset(divisible=True) - tx_create = Transaction.create([b.me], [([b.me, user_pk], 100)], - asset=asset) + tx_create = Transaction.create([b.me], [([b.me, user_pk], 100)]) tx_create_signed = tx_create.sign([b.me_private]) # create block block = b.create_block([tx_create_signed]) @@ -299,7 +278,7 @@ def test_single_in_multiple_own_single_out_single_own_transfer(b, user_pk, # TRANSFER tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)], - asset=tx_create.asset) + asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk]) assert tx_transfer_signed.validate(b) == tx_transfer_signed @@ -317,16 +296,14 @@ def test_single_in_multiple_own_single_out_single_own_transfer(b, user_pk, # Single owners_before per input # Single output # Single owners_after +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_multiple_in_single_own_single_out_single_own_transfer(b, user_pk, user_sk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset # CREATE divisible asset - asset = Asset(divisible=True) - tx_create = Transaction.create([b.me], [([user_pk], 50), ([user_pk], 50)], - asset=asset) + tx_create = Transaction.create([b.me], [([user_pk], 50), ([user_pk], 50)]) tx_create_signed = tx_create.sign([b.me_private]) # create block block = b.create_block([tx_create_signed]) @@ -338,7 +315,7 @@ def test_multiple_in_single_own_single_out_single_own_transfer(b, user_pk, # TRANSFER tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)], - asset=tx_create.asset) + asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) assert tx_transfer_signed.validate(b) @@ -352,18 +329,14 @@ def test_multiple_in_single_own_single_out_single_own_transfer(b, user_pk, # Multiple owners_before per input # Single output # Single owners_after +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_multiple_in_multiple_own_single_out_single_own_transfer(b, user_pk, user_sk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset # CREATE divisible asset - asset = Asset(divisible=True) - tx_create = Transaction.create([b.me], - [([user_pk, b.me], 50), - ([user_pk, b.me], 50)], - asset=asset) + tx_create = Transaction.create([b.me], [([user_pk, b.me], 50), ([user_pk, b.me], 50)]) tx_create_signed = tx_create.sign([b.me_private]) # create block block = b.create_block([tx_create_signed]) @@ -375,7 +348,7 @@ def test_multiple_in_multiple_own_single_out_single_own_transfer(b, user_pk, # TRANSFER tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)], - asset=tx_create.asset) + asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk]) assert tx_transfer_signed.validate(b) @@ -397,18 +370,14 @@ def test_multiple_in_multiple_own_single_out_single_own_transfer(b, user_pk, # owners_before # Single output # Single owners_after +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_muiltiple_in_mix_own_multiple_out_single_own_transfer(b, user_pk, user_sk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset # CREATE divisible asset - asset = Asset(divisible=True) - tx_create = Transaction.create([b.me], - [([user_pk], 50), - ([user_pk, b.me], 50)], - asset=asset) + tx_create = Transaction.create([b.me], [([user_pk], 50), ([user_pk, b.me], 50)]) tx_create_signed = tx_create.sign([b.me_private]) # create block block = b.create_block([tx_create_signed]) @@ -420,7 +389,7 @@ def test_muiltiple_in_mix_own_multiple_out_single_own_transfer(b, user_pk, # TRANSFER tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)], - asset=tx_create.asset) + asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk]) assert tx_transfer_signed.validate(b) == tx_transfer_signed @@ -442,18 +411,14 @@ def test_muiltiple_in_mix_own_multiple_out_single_own_transfer(b, user_pk, # Multiple outputs # Mix: one output with a single owners_after, one output with multiple # owners_after +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_muiltiple_in_mix_own_multiple_out_mix_own_transfer(b, user_pk, user_sk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset # CREATE divisible asset - asset = Asset(divisible=True) - tx_create = Transaction.create([b.me], - [([user_pk], 50), - ([user_pk, b.me], 50)], - asset=asset) + tx_create = Transaction.create([b.me], [([user_pk], 50), ([user_pk, b.me], 50)]) tx_create_signed = tx_create.sign([b.me_private]) # create block block = b.create_block([tx_create_signed]) @@ -466,7 +431,7 @@ def test_muiltiple_in_mix_own_multiple_out_mix_own_transfer(b, user_pk, # TRANSFER tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 50), ([b.me, user_pk], 50)], - asset=tx_create.asset) + asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk]) assert tx_transfer_signed.validate(b) == tx_transfer_signed @@ -493,19 +458,15 @@ def test_muiltiple_in_mix_own_multiple_out_mix_own_transfer(b, user_pk, # Single owners_before # Single output # Single owners_after +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_multiple_in_different_transactions(b, user_pk, user_sk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset # CREATE divisible asset # `b` creates a divisible asset and assigns 50 shares to `b` and # 50 shares to `user_pk` - asset = Asset(divisible=True) - tx_create = Transaction.create([b.me], - [([user_pk], 50), - ([b.me], 50)], - asset=asset) + tx_create = Transaction.create([b.me], [([user_pk], 50), ([b.me], 50)]) tx_create_signed = tx_create.sign([b.me_private]) # create block block = b.create_block([tx_create_signed]) @@ -521,7 +482,7 @@ def test_multiple_in_different_transactions(b, user_pk, user_sk): # split across two different transactions tx_transfer1 = Transaction.transfer(tx_create.to_inputs([1]), [([user_pk], 50)], - asset=tx_create.asset) + asset_id=tx_create.id) tx_transfer1_signed = tx_transfer1.sign([b.me_private]) # create block block = b.create_block([tx_transfer1_signed]) @@ -537,7 +498,7 @@ def test_multiple_in_different_transactions(b, user_pk, user_sk): tx_transfer2 = Transaction.transfer(tx_create.to_inputs([0]) + tx_transfer1.to_inputs([0]), [([b.me], 100)], - asset=tx_create.asset) + asset_id=tx_create.id) tx_transfer2_signed = tx_transfer2.sign([user_sk]) assert tx_transfer2_signed.validate(b) == tx_transfer2_signed @@ -554,15 +515,14 @@ def test_multiple_in_different_transactions(b, user_pk, user_sk): # In a TRANSFER transaction of a divisible asset the amount being spent in the # inputs needs to match the amount being sent in the outputs. # In other words `amount_in_inputs - amount_in_outputs == 0` +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_amount_error_transfer(b, user_pk, user_sk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset from bigchaindb.common.exceptions import AmountError # CREATE divisible asset - asset = Asset(divisible=True) - tx_create = Transaction.create([b.me], [([user_pk], 100)], asset=asset) + tx_create = Transaction.create([b.me], [([user_pk], 100)]) tx_create_signed = tx_create.sign([b.me_private]) # create block block = b.create_block([tx_create_signed]) @@ -575,7 +535,7 @@ def test_amount_error_transfer(b, user_pk, user_sk): # TRANSFER # output amount less than input amount tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 50)], - asset=tx_create.asset) + asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) with pytest.raises(AmountError): tx_transfer_signed.validate(b) @@ -583,13 +543,14 @@ def test_amount_error_transfer(b, user_pk, user_sk): # TRANSFER # output amount greater than input amount tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 101)], - asset=tx_create.asset) + asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) with pytest.raises(AmountError): tx_transfer_signed.validate(b) @pytest.mark.skip(reason='Figure out how to handle this case') +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_threshold_same_public_key(b, user_pk, user_sk): # If we try to fulfill a threshold condition where each subcondition has @@ -600,12 +561,9 @@ def test_threshold_same_public_key(b, user_pk, user_sk): # that does not mean that the code shouldn't work. from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset # CREATE divisible asset - asset = Asset(divisible=True) - tx_create = Transaction.create([b.me], [([user_pk, user_pk], 100)], - asset=asset) + tx_create = Transaction.create([b.me], [([user_pk, user_pk], 100)]) tx_create_signed = tx_create.sign([b.me_private]) # create block block = b.create_block([tx_create_signed]) @@ -617,24 +575,19 @@ def test_threshold_same_public_key(b, user_pk, user_sk): # TRANSFER tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)], - asset=tx_create.asset) + asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk, user_sk]) assert tx_transfer_signed.validate(b) == tx_transfer_signed +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_sum_amount(b, user_pk, user_sk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset # CREATE divisible asset with 3 outputs with amount 1 - asset = Asset(divisible=True) - tx_create = Transaction.create([b.me], - [([user_pk], 1), - ([user_pk], 1), - ([user_pk], 1)], - asset=asset) + tx_create = Transaction.create([b.me], [([user_pk], 1), ([user_pk], 1), ([user_pk], 1)]) tx_create_signed = tx_create.sign([b.me_private]) # create block block = b.create_block([tx_create_signed]) @@ -647,7 +600,7 @@ def test_sum_amount(b, user_pk, user_sk): # create a transfer transaction with one output and check if the amount # is 3 tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 3)], - asset=tx_create.asset) + asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) assert tx_transfer_signed.validate(b) == tx_transfer_signed @@ -655,15 +608,13 @@ def test_sum_amount(b, user_pk, user_sk): assert tx_transfer_signed.outputs[0].amount == 3 +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_divide(b, user_pk, user_sk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset # CREATE divisible asset with 1 output with amount 3 - asset = Asset(divisible=True) - tx_create = Transaction.create([b.me], [([user_pk], 3)], - asset=asset) + tx_create = Transaction.create([b.me], [([user_pk], 3)]) tx_create_signed = tx_create.sign([b.me_private]) # create block block = b.create_block([tx_create_signed]) @@ -677,7 +628,7 @@ def test_divide(b, user_pk, user_sk): # of each output is 1 tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 1), ([b.me], 1), ([b.me], 1)], - asset=tx_create.asset) + asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) assert tx_transfer_signed.validate(b) == tx_transfer_signed @@ -687,16 +638,14 @@ def test_divide(b, user_pk, user_sk): # Check that negative inputs are caught when creating a TRANSFER transaction +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_non_positive_amounts_on_transfer(b, user_pk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset from bigchaindb.common.exceptions import AmountError # CREATE divisible asset with 1 output with amount 3 - asset = Asset(divisible=True) - tx_create = Transaction.create([b.me], [([user_pk], 3)], - asset=asset) + tx_create = Transaction.create([b.me], [([user_pk], 3)]) tx_create_signed = tx_create.sign([b.me_private]) # create block block = b.create_block([tx_create_signed]) @@ -709,20 +658,18 @@ def test_non_positive_amounts_on_transfer(b, user_pk): with pytest.raises(AmountError): Transaction.transfer(tx_create.to_inputs(), [([b.me], 4), ([b.me], -1)], - asset=tx_create.asset) + asset_id=tx_create.id) # Check that negative inputs are caught when validating a TRANSFER transaction +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_non_positive_amounts_on_transfer_validate(b, user_pk, user_sk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset from bigchaindb.common.exceptions import AmountError # CREATE divisible asset with 1 output with amount 3 - asset = Asset(divisible=True) - tx_create = Transaction.create([b.me], [([user_pk], 3)], - asset=asset) + tx_create = Transaction.create([b.me], [([user_pk], 3)]) tx_create_signed = tx_create.sign([b.me_private]) # create block block = b.create_block([tx_create_signed]) @@ -736,7 +683,7 @@ def test_non_positive_amounts_on_transfer_validate(b, user_pk, user_sk): # of each output is 1 tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 4), ([b.me], 1)], - asset=tx_create.asset) + asset_id=tx_create.id) tx_transfer.outputs[1].amount = -1 tx_transfer_signed = tx_transfer.sign([user_sk]) @@ -745,33 +692,28 @@ def test_non_positive_amounts_on_transfer_validate(b, user_pk, user_sk): # Check that negative inputs are caught when creating a CREATE transaction +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_non_positive_amounts_on_create(b, user_pk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset from bigchaindb.common.exceptions import AmountError # CREATE divisible asset with 1 output with amount 3 - asset = Asset(divisible=True) with pytest.raises(AmountError): - Transaction.create([b.me], [([user_pk], -3)], - asset=asset) + Transaction.create([b.me], [([user_pk], -3)]) # Check that negative inputs are caught when validating a CREATE transaction +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_non_positive_amounts_on_create_validate(b, user_pk): from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset from bigchaindb.common.exceptions import AmountError # CREATE divisible asset with 1 output with amount 3 - asset = Asset(divisible=True) - tx_create = Transaction.create([b.me], [([user_pk], 3)], - asset=asset) + tx_create = Transaction.create([b.me], [([user_pk], 3)]) tx_create.outputs[0].amount = -3 - with patch.object(Asset, 'validate_asset', return_value=None): - tx_create_signed = tx_create.sign([b.me_private]) + tx_create_signed = tx_create.sign([b.me_private]) with pytest.raises(AmountError): tx_create_signed.validate(b) diff --git a/tests/backend/mongodb/__init__.py b/tests/backend/mongodb/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/backend/mongodb/test_connection.py b/tests/backend/mongodb/test_connection.py new file mode 100644 index 00000000..8d2c57b1 --- /dev/null +++ b/tests/backend/mongodb/test_connection.py @@ -0,0 +1,16 @@ + +def test_get_connection_returns_the_correct_instance(): + from bigchaindb.backend import connect + from bigchaindb.backend.connection import Connection + from bigchaindb.backend.mongodb.connection import MongoDBConnection + + config = { + 'backend': 'mongodb', + 'host': 'localhost', + 'port': 27017, + 'name': 'test' + } + + conn = connect(**config) + assert isinstance(conn, Connection) + assert isinstance(conn, MongoDBConnection) diff --git a/tests/backend/mongodb/test_schema.py b/tests/backend/mongodb/test_schema.py new file mode 100644 index 00000000..033d4113 --- /dev/null +++ b/tests/backend/mongodb/test_schema.py @@ -0,0 +1,156 @@ +import pytest +from unittest.mock import patch + +pytestmark = pytest.mark.bdb + + +def test_init_creates_db_tables_and_indexes(): + import bigchaindb + from bigchaindb import backend + from bigchaindb.backend.schema import init_database + + conn = backend.connect() + dbname = bigchaindb.config['database']['name'] + + # the db is set up by the fixture so we need to remove it + conn.conn.drop_database(dbname) + + init_database() + + collection_names = conn.conn[dbname].collection_names() + assert sorted(collection_names) == ['backlog', 'bigchain', 'votes'] + + indexes = conn.conn[dbname]['bigchain'].index_information().keys() + assert sorted(indexes) == ['_id_', 'asset_id', 'block_timestamp', + 'transaction_id'] + + indexes = conn.conn[dbname]['backlog'].index_information().keys() + assert sorted(indexes) == ['_id_', 'assignee__transaction_timestamp'] + + indexes = conn.conn[dbname]['votes'].index_information().keys() + assert sorted(indexes) == ['_id_', 'block_and_voter'] + + +def test_init_database_fails_if_db_exists(): + import bigchaindb + from bigchaindb import backend + from bigchaindb.backend.schema import init_database + from bigchaindb.common import exceptions + + conn = backend.connect() + dbname = bigchaindb.config['database']['name'] + + # The db is set up by the fixtures + assert dbname in conn.conn.database_names() + + with pytest.raises(exceptions.DatabaseAlreadyExists): + init_database() + + +def test_create_tables(): + import bigchaindb + from bigchaindb import backend + from bigchaindb.backend import schema + + conn = backend.connect() + dbname = bigchaindb.config['database']['name'] + + # The db is set up by the fixtures so we need to remove it + conn.conn.drop_database(dbname) + schema.create_database(conn, dbname) + schema.create_tables(conn, dbname) + + collection_names = conn.conn[dbname].collection_names() + assert sorted(collection_names) == ['backlog', 'bigchain', 'votes'] + + +def test_create_secondary_indexes(): + import bigchaindb + from bigchaindb import backend + from bigchaindb.backend import schema + + conn = backend.connect() + dbname = bigchaindb.config['database']['name'] + + # The db is set up by the fixtures so we need to remove it + conn.conn.drop_database(dbname) + schema.create_database(conn, dbname) + schema.create_tables(conn, dbname) + schema.create_indexes(conn, dbname) + + # Bigchain table + indexes = conn.conn[dbname]['bigchain'].index_information().keys() + assert sorted(indexes) == ['_id_', 'asset_id', 'block_timestamp', + 'transaction_id'] + + # Backlog table + indexes = conn.conn[dbname]['backlog'].index_information().keys() + assert sorted(indexes) == ['_id_', 'assignee__transaction_timestamp'] + + # Votes table + indexes = conn.conn[dbname]['votes'].index_information().keys() + assert sorted(indexes) == ['_id_', 'block_and_voter'] + + +def test_drop(): + import bigchaindb + from bigchaindb import backend + from bigchaindb.backend import schema + + conn = backend.connect() + dbname = bigchaindb.config['database']['name'] + + # The db is set up by fixtures + assert dbname in conn.conn.database_names() + + schema.drop_database(conn, dbname) + assert dbname not in conn.conn.database_names() + + +def test_get_replica_set_name_not_enabled(): + from pymongo.database import Database + from bigchaindb import backend + from bigchaindb.backend.mongodb.schema import _get_replica_set_name + from bigchaindb.common.exceptions import ConfigurationError + + conn = backend.connect() + + # no replSet option set + cmd_line_opts = {'argv': ['mongod', '--dbpath=/data'], + 'ok': 1.0, + 'parsed': {'storage': {'dbPath': '/data'}}} + with patch.object(Database, 'command', return_value=cmd_line_opts): + with pytest.raises(ConfigurationError): + _get_replica_set_name(conn) + + +def test_get_replica_set_name_command_line(): + from pymongo.database import Database + from bigchaindb import backend + from bigchaindb.backend.mongodb.schema import _get_replica_set_name + + conn = backend.connect() + + # replSet option set through the command line + cmd_line_opts = {'argv': ['mongod', '--dbpath=/data', '--replSet=rs0'], + 'ok': 1.0, + 'parsed': {'replication': {'replSet': 'rs0'}, + 'storage': {'dbPath': '/data'}}} + with patch.object(Database, 'command', return_value=cmd_line_opts): + assert _get_replica_set_name(conn) == 'rs0' + + +def test_get_replica_set_name_config_file(): + from pymongo.database import Database + from bigchaindb import backend + from bigchaindb.backend.mongodb.schema import _get_replica_set_name + + conn = backend.connect() + + # replSet option set through the config file + cmd_line_opts = {'argv': ['mongod', '--dbpath=/data', '--replSet=rs0'], + 'ok': 1.0, + 'parsed': {'replication': {'replSetName': 'rs0'}, + 'storage': {'dbPath': '/data'}}} + with patch.object(Database, 'command', return_value=cmd_line_opts): + assert _get_replica_set_name(conn) == 'rs0' diff --git a/tests/backend/rethinkdb/test_run_query_util.py b/tests/backend/rethinkdb/test_connection.py similarity index 78% rename from tests/backend/rethinkdb/test_run_query_util.py rename to tests/backend/rethinkdb/test_connection.py index 19cff15b..65c665af 100644 --- a/tests/backend/rethinkdb/test_run_query_util.py +++ b/tests/backend/rethinkdb/test_connection.py @@ -1,18 +1,39 @@ +import time +import multiprocessing as mp from threading import Thread -import pytest +import pytest import rethinkdb as r -from bigchaindb.backend import connect + +def test_get_connection_returns_the_correct_instance(): + from bigchaindb.backend import connect + from bigchaindb.backend.connection import Connection + from bigchaindb.backend.rethinkdb.connection import RethinkDBConnection + + config = { + 'backend': 'rethinkdb', + 'host': 'localhost', + 'port': 28015, + 'name': 'test' + } + + conn = connect(**config) + assert isinstance(conn, Connection) + assert isinstance(conn, RethinkDBConnection) def test_run_a_simple_query(): + from bigchaindb.backend import connect + conn = connect() query = r.expr('1') assert conn.run(query) == '1' def test_raise_exception_when_max_tries(): + from bigchaindb.backend import connect + class MockQuery: def run(self, conn): raise r.ReqlDriverError('mock') @@ -24,7 +45,7 @@ def test_raise_exception_when_max_tries(): def test_reconnect_when_connection_lost(): - import time + from bigchaindb.backend import connect def raise_exception(*args, **kwargs): raise r.ReqlDriverError('mock') @@ -44,9 +65,6 @@ def test_reconnect_when_connection_lost(): def test_changefeed_reconnects_when_connection_lost(monkeypatch): - import time - import multiprocessing as mp - from bigchaindb.backend.changefeed import ChangeFeed from bigchaindb.backend.rethinkdb.changefeed import RethinkDBChangeFeed @@ -64,7 +82,8 @@ def test_changefeed_reconnects_when_connection_lost(monkeypatch): if self.tries == 1: raise r.ReqlDriverError('mock') elif self.tries == 2: - return {'new_val': {'fact': 'A group of cats is called a clowder.'}, + return {'new_val': {'fact': + 'A group of cats is called a clowder.'}, 'old_val': None} if self.tries == 3: raise r.ReqlDriverError('mock') diff --git a/tests/backend/rethinkdb/test_schema.py b/tests/backend/rethinkdb/test_schema.py index 02545a45..1447e80f 100644 --- a/tests/backend/rethinkdb/test_schema.py +++ b/tests/backend/rethinkdb/test_schema.py @@ -6,7 +6,7 @@ from bigchaindb import backend from bigchaindb.backend.rethinkdb import schema -@pytest.mark.usefixtures('setup_database') +@pytest.mark.bdb def test_init_creates_db_tables_and_indexes(): from bigchaindb.backend.schema import init_database conn = backend.connect() @@ -28,7 +28,7 @@ def test_init_creates_db_tables_and_indexes(): 'assignee__transaction_timestamp')) is True -@pytest.mark.usefixtures('setup_database') +@pytest.mark.bdb def test_init_database_fails_if_db_exists(): from bigchaindb.backend.schema import init_database from bigchaindb.common import exceptions @@ -43,14 +43,13 @@ def test_init_database_fails_if_db_exists(): init_database() -def test_create_database(): +def test_create_database(not_yet_created_db): conn = backend.connect() - dbname = bigchaindb.config['database']['name'] - schema.create_database(conn, dbname) - assert conn.run(r.db_list().contains(dbname)) is True + schema.create_database(conn, not_yet_created_db) + assert conn.run(r.db_list().contains(not_yet_created_db)) is True -@pytest.mark.usefixtures('setup_database') +@pytest.mark.bdb def test_create_tables(): conn = backend.connect() dbname = bigchaindb.config['database']['name'] @@ -67,7 +66,7 @@ def test_create_tables(): assert len(conn.run(r.db(dbname).table_list())) == 3 -@pytest.mark.usefixtures('setup_database') +@pytest.mark.bdb def test_create_secondary_indexes(): conn = backend.connect() dbname = bigchaindb.config['database']['name'] @@ -96,29 +95,17 @@ def test_create_secondary_indexes(): 'block_and_voter')) is True -@pytest.mark.usefixtures('setup_database') -def test_drop(): +def test_drop(dummy_db): conn = backend.connect() - dbname = bigchaindb.config['database']['name'] - - # The db is set up by fixtures - assert conn.run(r.db_list().contains(dbname)) is True - - schema.drop_database(conn, dbname) - assert conn.run(r.db_list().contains(dbname)) is False + assert conn.run(r.db_list().contains(dummy_db)) is True + schema.drop_database(conn, dummy_db) + assert conn.run(r.db_list().contains(dummy_db)) is False -@pytest.mark.usefixtures('setup_database') -def test_drop_non_existent_db_raises_an_error(): +def test_drop_non_existent_db_raises_an_error(dummy_db): from bigchaindb.common import exceptions - conn = backend.connect() - dbname = bigchaindb.config['database']['name'] - - # The db is set up by fixtures - assert conn.run(r.db_list().contains(dbname)) is True - - schema.drop_database(conn, dbname) - + assert conn.run(r.db_list().contains(dummy_db)) is True + schema.drop_database(conn, dummy_db) with pytest.raises(exceptions.DatabaseDoesNotExist): - schema.drop_database(conn, dbname) + schema.drop_database(conn, dummy_db) diff --git a/tests/backend/test_connection.py b/tests/backend/test_connection.py index bdc336a9..7b49f45e 100644 --- a/tests/backend/test_connection.py +++ b/tests/backend/test_connection.py @@ -1,23 +1,6 @@ import pytest -def test_get_connection_returns_the_correct_instance(): - from bigchaindb.backend import connect - from bigchaindb.backend.connection import Connection - from bigchaindb.backend.rethinkdb.connection import RethinkDBConnection - - config = { - 'backend': 'rethinkdb', - 'host': 'localhost', - 'port': 28015, - 'name': 'test' - } - - conn = connect(**config) - assert isinstance(conn, Connection) - assert isinstance(conn, RethinkDBConnection) - - def test_get_connection_raises_a_configuration_error(monkeypatch): from bigchaindb.common.exceptions import ConfigurationError from bigchaindb.backend import connect @@ -28,6 +11,7 @@ def test_get_connection_raises_a_configuration_error(monkeypatch): with pytest.raises(ConfigurationError): # We need to force a misconfiguration here monkeypatch.setattr('bigchaindb.backend.connection.BACKENDS', - {'catsandra': 'bigchaindb.backend.meowmeow.Catsandra'}) + {'catsandra': + 'bigchaindb.backend.meowmeow.Catsandra'}) connect('catsandra', 'localhost', '1337', 'mydb') diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index fa238dfd..a3fc3503 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -101,7 +101,7 @@ def test_bigchain_run_start_assume_yes_create_default_config(monkeypatch, mock_p # TODO Please beware, that if debugging, the "-s" switch for pytest will # interfere with capsys. # See related issue: https://github.com/pytest-dev/pytest/issues/128 -@pytest.mark.usefixtures('restore_config') +@pytest.mark.usefixtures('ignore_local_config_file') def test_bigchain_show_config(capsys): from bigchaindb import config from bigchaindb.commands.bigchain import run_show_config @@ -229,7 +229,7 @@ def test_run_configure_when_config_does_exist(monkeypatch, @patch('bigchaindb.common.crypto.generate_key_pair', return_value=('private_key', 'public_key')) -@pytest.mark.usefixtures('restore_config') +@pytest.mark.usefixtures('ignore_local_config_file') def test_allow_temp_keypair_generates_one_on_the_fly(mock_gen_keypair, mock_processes_start, mock_db_init_with_existing_db): @@ -247,7 +247,7 @@ def test_allow_temp_keypair_generates_one_on_the_fly(mock_gen_keypair, @patch('bigchaindb.common.crypto.generate_key_pair', return_value=('private_key', 'public_key')) -@pytest.mark.usefixtures('restore_config') +@pytest.mark.usefixtures('ignore_local_config_file') def test_allow_temp_keypair_doesnt_override_if_keypair_found(mock_gen_keypair, mock_processes_start, mock_db_init_with_existing_db): diff --git a/tests/common/conftest.py b/tests/common/conftest.py index e906902a..e8c4f9c6 100644 --- a/tests/common/conftest.py +++ b/tests/common/conftest.py @@ -14,12 +14,19 @@ USER3_PUBLIC_KEY = 'Gbrg7JtxdjedQRmr81ZZbh1BozS7fBW88ZyxNDy7WLNC' CC_FULFILLMENT_URI = 'cf:0:' CC_CONDITION_URI = 'cc:0:3:47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU:0' +ASSET_DEFINITION = { + 'data': { + 'definition': 'Asset definition' + } +} + +ASSET_LINK = { + 'id': 'a' * 64 +} + DATA = { 'msg': 'Hello BigchainDB!' } -DATA_ID = '872fa6e6f46246cd44afdb2ee9cfae0e72885fb0910e2bcf9a5a2a4eadb417b8' - -UUID4 = 'dc568f27-a113-46b4-9bd4-43015859e3e3' @pytest.fixture @@ -121,25 +128,25 @@ def user2_output(user2_Ed25519, user2_pub): return Output(user2_Ed25519, [user2_pub]) +@pytest.fixture +def asset_definition(): + return ASSET_DEFINITION + + +@pytest.fixture +def asset_link(): + return ASSET_LINK + + @pytest.fixture def data(): return DATA -@pytest.fixture -def data_id(): - return DATA_ID - - -@pytest.fixture -def uuid4(): - return UUID4 - - @pytest.fixture def utx(user_input, user_output): - from bigchaindb.common.transaction import Transaction, Asset - return Transaction(Transaction.CREATE, Asset(), [user_input], + from bigchaindb.common.transaction import Transaction + return Transaction(Transaction.CREATE, {'data': None}, [user_input], [user_output]) @@ -151,12 +158,12 @@ def tx(utx, user_priv): @pytest.fixture def transfer_utx(user_output, user2_output, utx): from bigchaindb.common.transaction import (Input, TransactionLink, - Transaction, Asset) + Transaction) user_output = user_output.to_dict() input = Input(utx.outputs[0].fulfillment, user_output['public_keys'], TransactionLink(utx.id, 0)) - return Transaction('TRANSFER', Asset(), [input], [user2_output]) + return Transaction('TRANSFER', {'id': utx.id}, [input], [user2_output]) @pytest.fixture diff --git a/tests/common/test_asset.py b/tests/common/test_asset.py deleted file mode 100644 index f6a3f89d..00000000 --- a/tests/common/test_asset.py +++ /dev/null @@ -1,92 +0,0 @@ -from pytest import raises - - -def test_asset_default_values(): - from bigchaindb.common.transaction import Asset - - asset = Asset() - assert asset.data is None - assert asset.data_id - assert asset.divisible is False - assert asset.updatable is False - assert asset.refillable is False - - -def test_asset_creation_with_data(data): - from bigchaindb.common.transaction import Asset - - asset = Asset(data) - assert asset.data == data - - -def test_asset_invalid_asset_initialization(): - from bigchaindb.common.transaction import Asset - - # check types - with raises(TypeError): - Asset(data='some wrong type') - with raises(TypeError): - Asset(divisible=1) - with raises(TypeError): - Asset(refillable=1) - with raises(TypeError): - Asset(updatable=1) - - # check for features that are not yet implemented - with raises(NotImplementedError): - Asset(updatable=True) - with raises(NotImplementedError): - Asset(refillable=True) - - -def test_invalid_asset_comparison(data, data_id): - from bigchaindb.common.transaction import Asset - - assert Asset(data, data_id) != 'invalid comparison' - - -def test_asset_serialization(data, data_id): - from bigchaindb.common.transaction import Asset - - expected = { - 'id': data_id, - 'divisible': False, - 'updatable': False, - 'refillable': False, - 'data': data, - } - asset = Asset(data, data_id) - assert asset.to_dict() == expected - - -def test_asset_deserialization(data, data_id): - from bigchaindb.common.transaction import Asset - - asset_dict = { - 'id': data_id, - 'divisible': False, - 'updatable': False, - 'refillable': False, - 'data': data, - } - asset = Asset.from_dict(asset_dict) - expected = Asset(data, data_id) - assert asset == expected - - -def test_validate_asset(): - from bigchaindb.common.transaction import Asset - from bigchaindb.common.exceptions import AmountError - - # test amount errors - asset = Asset(divisible=False) - with raises(AmountError): - asset.validate_asset(amount=2) - - asset = Asset(divisible=True) - with raises(AmountError): - asset.validate_asset(amount=1) - - asset = Asset() - with raises(TypeError): - asset.validate_asset(amount='a') diff --git a/tests/common/test_transaction.py b/tests/common/test_transaction.py index c16d44f8..7ed70add 100644 --- a/tests/common/test_transaction.py +++ b/tests/common/test_transaction.py @@ -1,5 +1,4 @@ from pytest import raises -from unittest.mock import patch def test_input_serialization(ffill_uri, user_pub): @@ -145,9 +144,14 @@ def test_output_hashlock_deserialization(): def test_invalid_output_initialization(cond_uri, user_pub): from bigchaindb.common.transaction import Output + from bigchaindb.common.exceptions import AmountError with raises(TypeError): Output(cond_uri, user_pub) + with raises(TypeError): + Output(cond_uri, [user_pub], 'amount') + with raises(AmountError): + Output(cond_uri, [user_pub], 0) def test_generate_output_split_half_recursive(user_pub, user2_pub, @@ -241,58 +245,50 @@ def test_generate_output_invalid_parameters(user_pub, user2_pub, Output.generate([[user_pub]], 1) -def test_invalid_transaction_initialization(): - from bigchaindb.common.transaction import Transaction, Asset +def test_invalid_transaction_initialization(asset_definition): + from bigchaindb.common.transaction import Transaction with raises(ValueError): - Transaction(operation='invalid operation', asset=Asset()) + Transaction(operation='invalid operation', asset=asset_definition) with raises(TypeError): Transaction(operation='CREATE', asset='invalid asset') + with raises(TypeError): + Transaction(operation='TRANSFER', asset={}) with raises(TypeError): Transaction( operation='CREATE', - asset=Asset(), + asset=asset_definition, outputs='invalid outputs' ) with raises(TypeError): Transaction( operation='CREATE', - asset=Asset(), + asset=asset_definition, outputs=[], inputs='invalid inputs' ) with raises(TypeError): Transaction( operation='CREATE', - asset=Asset(), + asset=asset_definition, outputs=[], inputs=[], metadata='invalid metadata' ) -def test_create_default_asset_on_tx_initialization(): - from bigchaindb.common.transaction import Transaction, Asset - from bigchaindb.common.exceptions import ValidationError - from .util import validate_transaction_model +def test_create_default_asset_on_tx_initialization(asset_definition): + from bigchaindb.common.transaction import Transaction - with patch.object(Asset, 'validate_asset', return_value=None): - tx = Transaction(Transaction.CREATE, None) - expected = Asset() + expected = {'data': None} + tx = Transaction(Transaction.CREATE, asset=expected) asset = tx.asset - expected.data_id = None - asset.data_id = None assert asset == expected - # Fails because no asset hash - with raises(ValidationError): - validate_transaction_model(tx) - -def test_transaction_serialization(user_input, user_output, data, data_id): - from bigchaindb.common.transaction import Transaction, Asset - from bigchaindb.common.exceptions import ValidationError +def test_transaction_serialization(user_input, user_output, data): + from bigchaindb.common.transaction import Transaction from .util import validate_transaction_model tx_id = 'l0l' @@ -307,32 +303,23 @@ def test_transaction_serialization(user_input, user_output, data, data_id): 'operation': Transaction.CREATE, 'metadata': None, 'asset': { - 'id': data_id, - 'divisible': False, - 'updatable': False, - 'refillable': False, 'data': data, } } - tx = Transaction(Transaction.CREATE, Asset(data, data_id), [user_input], + tx = Transaction(Transaction.CREATE, {'data': data}, [user_input], [user_output]) tx_dict = tx.to_dict() tx_dict['id'] = tx_id - tx_dict['asset']['id'] = data_id assert tx_dict == expected - # Fails because asset id is not a uuid4 - with raises(ValidationError): - validate_transaction_model(tx) - -def test_transaction_deserialization(user_input, user_output, data, uuid4): - from bigchaindb.common.transaction import Transaction, Asset +def test_transaction_deserialization(user_input, user_output, data): + from bigchaindb.common.transaction import Transaction from .util import validate_transaction_model - expected_asset = Asset(data, uuid4) + expected_asset = {'data': data} expected = Transaction(Transaction.CREATE, expected_asset, [user_input], [user_output], None, Transaction.VERSION) @@ -345,10 +332,6 @@ def test_transaction_deserialization(user_input, user_output, data, uuid4): 'operation': Transaction.CREATE, 'metadata': None, 'asset': { - 'id': uuid4, - 'divisible': False, - 'updatable': False, - 'refillable': False, 'data': data, } } @@ -454,92 +437,28 @@ def test_cast_transaction_link_to_boolean(): assert bool(TransactionLink(False, False)) is True -def test_asset_link_serialization(): - from bigchaindb.common.transaction import AssetLink +def test_add_input_to_tx(user_input, asset_definition): + from bigchaindb.common.transaction import Transaction - data_id = 'a asset id' - expected = { - 'id': data_id, - } - asset_link = AssetLink(data_id) - - assert asset_link.to_dict() == expected - - -def test_asset_link_serialization_with_empty_payload(): - from bigchaindb.common.transaction import AssetLink - - expected = None - asset_link = AssetLink() - - assert asset_link.to_dict() == expected - - -def test_asset_link_deserialization(): - from bigchaindb.common.transaction import AssetLink - - data_id = 'a asset id' - expected = AssetLink(data_id) - asset_link = { - 'id': data_id - } - asset_link = AssetLink.from_dict(asset_link) - - assert asset_link == expected - - -def test_asset_link_deserialization_with_empty_payload(): - from bigchaindb.common.transaction import AssetLink - - expected = AssetLink() - asset_link = AssetLink.from_dict(None) - - assert asset_link == expected - - -def test_cast_asset_link_to_boolean(): - from bigchaindb.common.transaction import AssetLink - - assert bool(AssetLink()) is False - assert bool(AssetLink('a')) is True - assert bool(AssetLink(False)) is True - - -def test_eq_asset_link(): - from bigchaindb.common.transaction import AssetLink - - asset_id_1 = 'asset_1' - asset_id_2 = 'asset_2' - - assert AssetLink(asset_id_1) == AssetLink(asset_id_1) - assert AssetLink(asset_id_1) != AssetLink(asset_id_2) - - -def test_add_input_to_tx(user_input): - from bigchaindb.common.transaction import Transaction, Asset - - with patch.object(Asset, 'validate_asset', return_value=None): - tx = Transaction(Transaction.CREATE, Asset(), [], []) + tx = Transaction(Transaction.CREATE, asset_definition, [], []) tx.add_input(user_input) assert len(tx.inputs) == 1 -def test_add_input_to_tx_with_invalid_parameters(): - from bigchaindb.common.transaction import Transaction, Asset +def test_add_input_to_tx_with_invalid_parameters(asset_definition): + from bigchaindb.common.transaction import Transaction + tx = Transaction(Transaction.CREATE, asset_definition) - with patch.object(Asset, 'validate_asset', return_value=None): - tx = Transaction(Transaction.CREATE, Asset()) with raises(TypeError): tx.add_input('somewronginput') -def test_add_output_to_tx(user_output): - from bigchaindb.common.transaction import Transaction, Asset +def test_add_output_to_tx(user_output, asset_definition): + from bigchaindb.common.transaction import Transaction from .util import validate_transaction_model - with patch.object(Asset, 'validate_asset', return_value=None): - tx = Transaction(Transaction.CREATE, Asset()) + tx = Transaction(Transaction.CREATE, asset_definition) tx.add_output(user_output) assert len(tx.outputs) == 1 @@ -547,11 +466,10 @@ def test_add_output_to_tx(user_output): validate_transaction_model(tx) -def test_add_output_to_tx_with_invalid_parameters(): - from bigchaindb.common.transaction import Transaction, Asset +def test_add_output_to_tx_with_invalid_parameters(asset_definition): + from bigchaindb.common.transaction import Transaction + tx = Transaction(Transaction.CREATE, asset_definition, [], []) - with patch.object(Asset, 'validate_asset', return_value=None): - tx = Transaction(Transaction.CREATE, Asset(), [], []) with raises(TypeError): tx.add_output('somewronginput') @@ -563,13 +481,14 @@ def test_sign_with_invalid_parameters(utx, user_priv): utx.sign(user_priv) -def test_validate_tx_simple_create_signature(user_input, user_output, user_priv): +def test_validate_tx_simple_create_signature(user_input, user_output, user_priv, + asset_definition): from copy import deepcopy from bigchaindb.common.crypto import PrivateKey - from bigchaindb.common.transaction import Transaction, Asset + from bigchaindb.common.transaction import Transaction from .util import validate_transaction_model - tx = Transaction(Transaction.CREATE, Asset(), [user_input], [user_output]) + tx = Transaction(Transaction.CREATE, asset_definition, [user_input], [user_output]) expected = deepcopy(user_output) expected.fulfillment.sign(str(tx).encode(), PrivateKey(user_priv)) tx.sign([user_priv]) @@ -621,14 +540,15 @@ def test_validate_input_with_invalid_parameters(utx): assert not valid -def test_validate_multiple_inputs(user_input, user_output, user_priv): +def test_validate_multiple_inputs(user_input, user_output, user_priv, + asset_definition): from copy import deepcopy from bigchaindb.common.crypto import PrivateKey - from bigchaindb.common.transaction import Transaction, Asset + from bigchaindb.common.transaction import Transaction from .util import validate_transaction_model - tx = Transaction(Transaction.CREATE, Asset(divisible=True), + tx = Transaction(Transaction.CREATE, asset_definition, [user_input, deepcopy(user_input)], [user_output, deepcopy(user_output)]) @@ -659,14 +579,16 @@ def test_validate_tx_threshold_create_signature(user_user2_threshold_input, user_pub, user2_pub, user_priv, - user2_priv): + user2_priv, + asset_definition): from copy import deepcopy from bigchaindb.common.crypto import PrivateKey - from bigchaindb.common.transaction import Transaction, Asset + from bigchaindb.common.transaction import Transaction from .util import validate_transaction_model - tx = Transaction(Transaction.CREATE, Asset(), [user_user2_threshold_input], + tx = Transaction(Transaction.CREATE, asset_definition, + [user_user2_threshold_input], [user_user2_threshold_output]) expected = deepcopy(user_user2_threshold_output) expected.fulfillment.subconditions[0]['body'].sign(str(tx).encode(), @@ -685,14 +607,15 @@ def test_validate_tx_threshold_create_signature(user_user2_threshold_input, def test_multiple_input_validation_of_transfer_tx(user_input, user_output, user_priv, user2_pub, user2_priv, user3_pub, - user3_priv): + user3_priv, + asset_definition): from copy import deepcopy from bigchaindb.common.transaction import (Transaction, TransactionLink, - Input, Output, Asset) + Input, Output) from cryptoconditions import Ed25519Fulfillment from .util import validate_transaction_model - tx = Transaction(Transaction.CREATE, Asset(divisible=True), + tx = Transaction(Transaction.CREATE, asset_definition, [user_input, deepcopy(user_input)], [user_output, deepcopy(user_output)]) tx.sign([user_priv]) @@ -702,7 +625,7 @@ def test_multiple_input_validation_of_transfer_tx(user_input, user_output, for index, cond in enumerate(tx.outputs)] outputs = [Output(Ed25519Fulfillment(public_key=user3_pub), [user3_pub]), Output(Ed25519Fulfillment(public_key=user3_pub), [user3_pub])] - transfer_tx = Transaction('TRANSFER', tx.asset, inputs, outputs) + transfer_tx = Transaction('TRANSFER', {'id': tx.id}, inputs, outputs) transfer_tx = transfer_tx.sign([user_priv]) assert transfer_tx.inputs_valid(tx.outputs) is True @@ -732,18 +655,14 @@ def test_validate_inputs_of_transfer_tx_with_invalid_params( transfer_tx.inputs_valid([utx.outputs[0]]) -def test_create_create_transaction_single_io(user_output, user_pub, data, uuid4): - from bigchaindb.common.transaction import Transaction, Asset +def test_create_create_transaction_single_io(user_output, user_pub, data): + from bigchaindb.common.transaction import Transaction from .util import validate_transaction_model expected = { 'outputs': [user_output.to_dict()], 'metadata': data, 'asset': { - 'id': uuid4, - 'divisible': False, - 'updatable': False, - 'refillable': False, 'data': data, }, 'inputs': [ @@ -759,8 +678,8 @@ def test_create_create_transaction_single_io(user_output, user_pub, data, uuid4) 'version': 1, } - asset = Asset(data, uuid4) - tx = Transaction.create([user_pub], [([user_pub], 1)], data, asset) + tx = Transaction.create([user_pub], [([user_pub], 1)], metadata=data, + asset=data) tx_dict = tx.to_dict() tx_dict['inputs'][0]['fulfillment'] = None tx_dict.pop('id') @@ -770,17 +689,18 @@ def test_create_create_transaction_single_io(user_output, user_pub, data, uuid4) validate_transaction_model(tx) -def test_validate_single_io_create_transaction(user_pub, user_priv, data): - from bigchaindb.common.transaction import Transaction, Asset +def test_validate_single_io_create_transaction(user_pub, user_priv, data, + asset_definition): + from bigchaindb.common.transaction import Transaction - tx = Transaction.create([user_pub], [([user_pub], 1)], data, Asset()) + tx = Transaction.create([user_pub], [([user_pub], 1)], metadata=data) tx = tx.sign([user_priv]) assert tx.inputs_valid() is True def test_create_create_transaction_multiple_io(user_output, user2_output, user_pub, - user2_pub): - from bigchaindb.common.transaction import Transaction, Asset, Input + user2_pub, asset_definition): + from bigchaindb.common.transaction import Transaction, Input # a fulfillment for a create transaction with multiple `owners_before` # is a fulfillment for an implicit threshold condition with @@ -795,10 +715,8 @@ def test_create_create_transaction_multiple_io(user_output, user2_output, user_p 'operation': 'CREATE', 'version': 1 } - asset = Asset(divisible=True) tx = Transaction.create([user_pub, user2_pub], [([user_pub], 1), ([user2_pub], 1)], - asset=asset, metadata={'message': 'hello'}).to_dict() tx.pop('id') tx.pop('asset') @@ -807,14 +725,14 @@ def test_create_create_transaction_multiple_io(user_output, user2_output, user_p def test_validate_multiple_io_create_transaction(user_pub, user_priv, - user2_pub, user2_priv): - from bigchaindb.common.transaction import Transaction, Asset + user2_pub, user2_priv, + asset_definition): + from bigchaindb.common.transaction import Transaction from .util import validate_transaction_model tx = Transaction.create([user_pub, user2_pub], [([user_pub], 1), ([user2_pub], 1)], - metadata={'message': 'hello'}, - asset=Asset(divisible=True)) + metadata={'message': 'hello'}) tx = tx.sign([user_priv, user2_priv]) assert tx.inputs_valid() is True @@ -823,18 +741,13 @@ def test_validate_multiple_io_create_transaction(user_pub, user_priv, def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, user_user2_threshold_output, - user_user2_threshold_input, data, - uuid4): - from bigchaindb.common.transaction import Transaction, Asset + user_user2_threshold_input, data): + from bigchaindb.common.transaction import Transaction expected = { 'outputs': [user_user2_threshold_output.to_dict()], 'metadata': data, 'asset': { - 'id': uuid4, - 'divisible': False, - 'updatable': False, - 'refillable': False, 'data': data, }, 'inputs': [ @@ -849,9 +762,8 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, 'operation': 'CREATE', 'version': 1 } - asset = Asset(data, uuid4) tx = Transaction.create([user_pub], [([user_pub, user2_pub], 1)], - data, asset) + metadata=data, asset=data) tx_dict = tx.to_dict() tx_dict.pop('id') tx_dict['inputs'][0]['fulfillment'] = None @@ -860,12 +772,12 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, def test_validate_threshold_create_transaction(user_pub, user_priv, user2_pub, - data): - from bigchaindb.common.transaction import Transaction, Asset + data, asset_definition): + from bigchaindb.common.transaction import Transaction from .util import validate_transaction_model tx = Transaction.create([user_pub], [([user_pub, user2_pub], 1)], - data, Asset()) + metadata=data) tx = tx.sign([user_priv]) assert tx.inputs_valid() is True @@ -887,6 +799,13 @@ def test_create_create_transaction_with_invalid_parameters(user_pub): Transaction.create([user_pub], [user_pub]) with raises(ValueError): Transaction.create([user_pub], [([user_pub],)]) + with raises(TypeError): + Transaction.create([user_pub], [([user_pub], 1)], + metadata='not a dict or none') + with raises(TypeError): + Transaction.create([user_pub], + [([user_pub], 1)], + asset='not a dict or none') def test_outputs_to_inputs(tx): @@ -900,10 +819,10 @@ def test_outputs_to_inputs(tx): def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, - user2_output, user_priv, uuid4): + user2_output, user_priv): from copy import deepcopy from bigchaindb.common.crypto import PrivateKey - from bigchaindb.common.transaction import Transaction, Asset + from bigchaindb.common.transaction import Transaction from bigchaindb.common.util import serialize from .util import validate_transaction_model @@ -911,7 +830,7 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, 'outputs': [user2_output.to_dict()], 'metadata': None, 'asset': { - 'id': uuid4, + 'id': tx.id, }, 'inputs': [ { @@ -929,8 +848,8 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, 'version': 1 } inputs = tx.to_inputs([0]) - asset = Asset(None, uuid4) - transfer_tx = Transaction.transfer(inputs, [([user2_pub], 1)], asset=asset) + transfer_tx = Transaction.transfer(inputs, [([user2_pub], 1)], + asset_id=tx.id) transfer_tx = transfer_tx.sign([user_priv]) transfer_tx = transfer_tx.to_dict() @@ -951,12 +870,12 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, def test_create_transfer_transaction_multiple_io(user_pub, user_priv, user2_pub, user2_priv, - user3_pub, user2_output): - from bigchaindb.common.transaction import Transaction, Asset + user3_pub, user2_output, + asset_definition): + from bigchaindb.common.transaction import Transaction - asset = Asset(divisible=True) tx = Transaction.create([user_pub], [([user_pub], 1), ([user2_pub], 1)], - asset=asset, metadata={'message': 'hello'}) + metadata={'message': 'hello'}) tx = tx.sign([user_priv]) expected = { @@ -989,7 +908,7 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv, transfer_tx = Transaction.transfer(tx.to_inputs(), [([user2_pub], 1), ([user2_pub], 1)], - asset=tx.asset) + asset_id=tx.id) transfer_tx = transfer_tx.sign([user_priv, user2_priv]) assert len(transfer_tx.inputs) == 2 @@ -1006,36 +925,40 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv, assert expected == transfer_tx -def test_create_transfer_with_invalid_parameters(user_pub): - from bigchaindb.common.transaction import Transaction, Asset +def test_create_transfer_with_invalid_parameters(tx, user_pub): + from bigchaindb.common.transaction import Transaction with raises(TypeError): - Transaction.transfer({}, [], Asset()) + Transaction.transfer({}, [], tx.id) with raises(ValueError): - Transaction.transfer([], [], Asset()) + Transaction.transfer([], [], tx.id) with raises(TypeError): - Transaction.transfer(['fulfillment'], {}, Asset()) + Transaction.transfer(['fulfillment'], {}, tx.id) with raises(ValueError): - Transaction.transfer(['fulfillment'], [], Asset()) + Transaction.transfer(['fulfillment'], [], tx.id) with raises(ValueError): - Transaction.transfer(['fulfillment'], [user_pub], Asset()) + Transaction.transfer(['fulfillment'], [user_pub], tx.id) with raises(ValueError): - Transaction.transfer(['fulfillment'], [([user_pub],)], Asset()) + Transaction.transfer(['fulfillment'], [([user_pub],)], tx.id) + with raises(TypeError): + Transaction.transfer(['fulfillment'], [([user_pub], 1)], + tx.id, metadata='not a dict or none') + with raises(TypeError): + Transaction.transfer(['fulfillment'], [([user_pub], 1)], + ['not a string']) def test_cant_add_empty_output(): - from bigchaindb.common.transaction import Transaction, Asset + from bigchaindb.common.transaction import Transaction + tx = Transaction(Transaction.CREATE, None) - with patch.object(Asset, 'validate_asset', return_value=None): - tx = Transaction(Transaction.CREATE, None) with raises(TypeError): tx.add_output(None) def test_cant_add_empty_input(): - from bigchaindb.common.transaction import Transaction, Asset + from bigchaindb.common.transaction import Transaction + tx = Transaction(Transaction.CREATE, None) - with patch.object(Asset, 'validate_asset', return_value=None): - tx = Transaction(Transaction.CREATE, None) with raises(TypeError): tx.add_input(None) diff --git a/tests/conftest.py b/tests/conftest.py index b71ac9fa..9a6b7a73 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,24 +8,15 @@ Tasks: import os import copy +import random import pytest from bigchaindb.common import crypto +TEST_DB_NAME = 'bigchain_test' USER2_SK, USER2_PK = crypto.generate_key_pair() -DB_NAME = 'bigchain_test_{}'.format(os.getpid()) - -CONFIG = { - 'database': { - 'name': DB_NAME, - }, - 'keypair': { - 'private': '31Lb1ZGKTyHnmVK3LUMrAUrPNfd4sE2YyBt3UA4A25aA', - 'public': '4XYfCbabAWVUCbjTmRTFEu2sc3dFEdkse4r6X498B1s8', - } -} # Test user. inputs will be created for this user. Cryptography Keys USER_PRIVATE_KEY = '8eJ8q9ZQpReWyQT5aFCiwtZ5wDZC4eDnCen88p3tQ6ie' @@ -55,6 +46,142 @@ def pytest_ignore_collect(path, config): return True +def pytest_configure(config): + config.addinivalue_line( + 'markers', + 'bdb(): Mark the test as needing BigchainDB, i.e. a database with ' + 'the three tables: "backlog", "bigchain", "votes". BigchainDB will ' + 'be configured such that the database and tables are available for an ' + 'entire test session. For distributed tests, the database name will ' + 'be suffixed with the process identifier, e.g.: "bigchain_test_gw0", ' + 'to ensure that each process session has its own separate database.' + ) + config.addinivalue_line( + 'markers', + 'genesis(): Mark the test as needing a genesis block in place. The ' + 'prerequisite steps of configuration and database setup are taken ' + 'care of at session scope (if needed), prior to creating the genesis ' + 'block. The genesis block has function scope: it is destroyed after ' + 'each test function/method.' + ) + + +@pytest.fixture(autouse=True) +def _bdb_marker(request): + if request.keywords.get('bdb', None): + request.getfixturevalue('_bdb') + + +@pytest.fixture(autouse=True) +def _genesis_marker(request): + if request.keywords.get('genesis', None): + request.getfixturevalue('_genesis') + + +@pytest.fixture(autouse=True) +def _restore_config(_configure_bigchaindb): + from bigchaindb import config, config_utils + config_before_test = copy.deepcopy(config) + yield + config_utils.set_config(config_before_test) + + +@pytest.fixture +def _restore_dbs(request): + from bigchaindb.backend import connect, schema + from bigchaindb.common.exceptions import DatabaseDoesNotExist + from .utils import list_dbs + conn = connect() + dbs_before_test = list_dbs(conn) + yield + dbs_after_test = list_dbs(conn) + dbs_to_delete = ( + db for db in set(dbs_after_test) - set(dbs_before_test) + if TEST_DB_NAME not in db + ) + print(dbs_to_delete) + for db in dbs_to_delete: + try: + schema.drop_database(conn, db) + except DatabaseDoesNotExist: + pass + + +@pytest.fixture(scope='session') +def _configure_bigchaindb(request): + from bigchaindb import config_utils + test_db_name = TEST_DB_NAME + # Put a suffix like _gw0, _gw1 etc on xdist processes + xdist_suffix = getattr(request.config, 'slaveinput', {}).get('slaveid') + if xdist_suffix: + test_db_name = '{}_{}'.format(TEST_DB_NAME, xdist_suffix) + config = { + 'database': { + 'name': test_db_name, + 'backend': request.config.getoption('--database-backend'), + }, + 'keypair': { + 'private': '31Lb1ZGKTyHnmVK3LUMrAUrPNfd4sE2YyBt3UA4A25aA', + 'public': '4XYfCbabAWVUCbjTmRTFEu2sc3dFEdkse4r6X498B1s8', + } + } + # FIXME + if config['database']['backend'] == 'mongodb': + # not a great way to do this + config['database']['port'] = 27017 + config_utils.set_config(config) + + +@pytest.fixture(scope='session') +def _setup_database(_configure_bigchaindb): + from bigchaindb import config + from bigchaindb.backend import connect, schema + from bigchaindb.common.exceptions import DatabaseDoesNotExist + print('Initializing test db') + dbname = config['database']['name'] + conn = connect() + + try: + schema.drop_database(conn, dbname) + except DatabaseDoesNotExist: + pass + + schema.init_database(conn) + print('Finishing init database') + + yield + + print('Deleting `{}` database'.format(dbname)) + conn = connect() + try: + schema.drop_database(conn, dbname) + except DatabaseDoesNotExist: + pass + + print('Finished deleting `{}`'.format(dbname)) + + +@pytest.fixture +def _bdb(_setup_database, _configure_bigchaindb): + yield + from bigchaindb import config + from bigchaindb.backend import connect + from .utils import flush_db + dbname = config['database']['name'] + conn = connect() + flush_db(conn, dbname) + + +@pytest.fixture +def _genesis(_bdb, genesis_block): + # TODO for precision's sake, delete the block once the test is done. The + # deletion is done indirectly via the teardown code of _bdb but explicit + # deletion of the block would make things clearer. E.g.: + # yield + # tests.utils.delete_genesis_block(conn, dbname) + pass + + # We need this function to avoid loading an existing # conf file located in the home of the user running # the tests. If it's too aggressive we can change it @@ -68,22 +195,6 @@ def ignore_local_config_file(monkeypatch): mock_file_config) -@pytest.fixture -def restore_config(ignore_local_config_file, node_config): - from bigchaindb import config_utils - config_utils.set_config(node_config) - - -@pytest.fixture(scope='module') -def node_config(request): - config = copy.deepcopy(CONFIG) - config['database']['backend'] = request.config.getoption('--database-backend') - if config['database']['backend'] == 'mongodb': - # not a great way to do this - config['database']['port'] = 27017 - return config - - @pytest.fixture def user_sk(): return USER_PRIVATE_KEY @@ -105,7 +216,7 @@ def user2_pk(): @pytest.fixture -def b(restore_config): +def b(): from bigchaindb import Bigchain return Bigchain() @@ -125,7 +236,7 @@ def signed_create_tx(b, create_tx): def signed_transfer_tx(signed_create_tx, user_pk, user_sk): from bigchaindb.models import Transaction inputs = signed_create_tx.to_inputs() - tx = Transaction.transfer(inputs, [([user_pk], 1)], signed_create_tx.asset) + tx = Transaction.transfer(inputs, [([user_pk], 1)], asset_id=signed_create_tx.id) return tx.sign([user_sk]) @@ -145,85 +256,91 @@ def structurally_valid_vote(): @pytest.fixture -def setup_database(restore_config, node_config): +def genesis_block(b): + return b.create_genesis_block() + + +@pytest.fixture +def inputs(user_pk, b, genesis_block): + from bigchaindb.models import Transaction + + # create blocks with transactions for `USER` to spend + prev_block_id = genesis_block.id + for block in range(4): + transactions = [ + Transaction.create([b.me], [([user_pk], 1)], + metadata={'msg': random.random()}) + .sign([b.me_private]) + for _ in range(10) + ] + block = b.create_block(transactions) + b.write_block(block) + + # vote the blocks valid, so that the inputs are valid + vote = b.vote(block.id, prev_block_id, True) + prev_block_id = block.id + b.write_vote(vote) + + +@pytest.fixture +def inputs_shared(user_pk, user2_pk, genesis_block): + from bigchaindb.models import Transaction + + # create blocks with transactions for `USER` to spend + prev_block_id = genesis_block.id + for block in range(4): + transactions = [ + Transaction.create([b.me], [user_pk, user2_pk], + metadata={'msg': random.random()}) + .sign([b.me_private]) + for _ in range(10) + ] + block = b.create_block(transactions) + b.write_block(block) + + # vote the blocks valid, so that the inputs are valid + vote = b.vote(block.id, prev_block_id, True) + prev_block_id = block.id + b.write_vote(vote) + + +@pytest.fixture +def dummy_db(request): + from bigchaindb.backend import connect, schema + from bigchaindb.common.exceptions import (DatabaseDoesNotExist, + DatabaseAlreadyExists) + conn = connect() + dbname = request.fixturename + xdist_suffix = getattr(request.config, 'slaveinput', {}).get('slaveid') + if xdist_suffix: + dbname = '{}_{}'.format(dbname, xdist_suffix) + try: + schema.create_database(conn, dbname) + except DatabaseAlreadyExists: + schema.drop_database(conn, dbname) + schema.create_database(conn, dbname) + yield dbname + try: + schema.drop_database(conn, dbname) + except DatabaseDoesNotExist: + pass + + +@pytest.fixture +def not_yet_created_db(request): from bigchaindb.backend import connect, schema from bigchaindb.common.exceptions import DatabaseDoesNotExist - print('Initializing test db') - db_name = node_config['database']['name'] conn = connect() - + dbname = request.fixturename + xdist_suffix = getattr(request.config, 'slaveinput', {}).get('slaveid') + if xdist_suffix: + dbname = '{}_{}'.format(dbname, xdist_suffix) try: - schema.drop_database(conn, db_name) + schema.drop_database(conn, dbname) except DatabaseDoesNotExist: pass - - schema.init_database(conn) - print('Finishing init database') - - yield - - print('Deleting `{}` database'.format(db_name)) - conn = connect() + yield dbname try: - schema.drop_database(conn, db_name) + schema.drop_database(conn, dbname) except DatabaseDoesNotExist: pass - - print('Finished deleting `{}`'.format(db_name)) - - -@pytest.fixture -def inputs(user_pk, setup_database): - from bigchaindb import Bigchain - from bigchaindb.models import Transaction - from bigchaindb.common.exceptions import GenesisBlockAlreadyExistsError - # 1. create the genesis block - b = Bigchain() - try: - g = b.create_genesis_block() - except GenesisBlockAlreadyExistsError: - pass - - # 2. create blocks with transactions for `USER` to spend - prev_block_id = g.id - for block in range(4): - transactions = [ - Transaction.create([b.me], [([user_pk], 1)]).sign([b.me_private]) - for i in range(10) - ] - block = b.create_block(transactions) - b.write_block(block) - - # 3. vote the blocks valid, so that the inputs are valid - vote = b.vote(block.id, prev_block_id, True) - prev_block_id = block.id - b.write_vote(vote) - - -@pytest.fixture -def inputs_shared(user_pk, user2_pk, setup_database): - from bigchaindb import Bigchain - from bigchaindb.models import Transaction - from bigchaindb.common.exceptions import GenesisBlockAlreadyExistsError - # 1. create the genesis block - b = Bigchain() - try: - g = b.create_genesis_block() - except GenesisBlockAlreadyExistsError: - pass - - # 2. create blocks with transactions for `USER` to spend - prev_block_id = g.id - for block in range(4): - transactions = [ - Transaction.create( - [b.me], [user_pk, user2_pk], payload={'i': i}).sign([b.me_private]) - for i in range(10) - ] - block = b.create_block(transactions) - b.write_block(block) - - # 3. vote the blocks valid, so that the inputs are valid - vote = b.vote(block.id, prev_block_id, True) - prev_block_id = block.id - b.write_vote(vote) diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index a0af611d..09152f14 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -1,6 +1,9 @@ from time import sleep import pytest +import random + +pytestmark = pytest.mark.bdb @pytest.mark.skipif(reason='Some tests throw a ResourceWarning that might result in some weird ' @@ -28,16 +31,15 @@ def dummy_block(): return block -@pytest.mark.usefixtures('setup_database') class TestBigchainApi(object): + + @pytest.mark.genesis def test_get_last_voted_block_cyclic_blockchain(self, b, monkeypatch): from bigchaindb.common.crypto import PrivateKey from bigchaindb.common.exceptions import CyclicBlockchainError from bigchaindb.common.util import serialize from bigchaindb.models import Transaction - b.create_genesis_block() - tx = Transaction.create([b.me], [([b.me], 1)]) tx = tx.sign([b.me_private]) monkeypatch.setattr('time.time', lambda: 1) @@ -54,13 +56,12 @@ class TestBigchainApi(object): with pytest.raises(CyclicBlockchainError): b.get_last_voted_block() + @pytest.mark.genesis def test_try_voting_while_constructing_cyclic_blockchain(self, b, monkeypatch): from bigchaindb.common.exceptions import CyclicBlockchainError from bigchaindb.models import Transaction - b.create_genesis_block() - tx = Transaction.create([b.me], [([b.me], 1)]) tx = tx.sign([b.me_private]) block1 = b.create_block([tx]) @@ -70,11 +71,10 @@ class TestBigchainApi(object): with pytest.raises(CyclicBlockchainError): b.vote(block1.id, block1.id, True) + @pytest.mark.genesis def test_has_previous_vote_when_already_voted(self, b, monkeypatch): from bigchaindb.models import Transaction - b.create_genesis_block() - tx = Transaction.create([b.me], [([b.me], 1)]) tx = tx.sign([b.me_private]) @@ -89,12 +89,11 @@ class TestBigchainApi(object): assert b.has_previous_vote(block.id, block.voters) is True + @pytest.mark.genesis def test_get_spent_with_double_spend(self, b, monkeypatch): from bigchaindb.common.exceptions import DoubleSpend from bigchaindb.models import Transaction - b.create_genesis_block() - tx = Transaction.create([b.me], [([b.me], 1)]) tx = tx.sign([b.me_private]) @@ -104,14 +103,14 @@ class TestBigchainApi(object): monkeypatch.setattr('time.time', lambda: 2) transfer_tx = Transaction.transfer(tx.to_inputs(), [([b.me], 1)], - tx.asset) + asset_id=tx.id) transfer_tx = transfer_tx.sign([b.me_private]) block2 = b.create_block([transfer_tx]) b.write_block(block2) monkeypatch.setattr('time.time', lambda: 3333333333) transfer_tx2 = Transaction.transfer(tx.to_inputs(), [([b.me], 1)], - tx.asset) + asset_id=tx.id) transfer_tx2 = transfer_tx2.sign([b.me_private]) block3 = b.create_block([transfer_tx2]) b.write_block(block3) @@ -125,12 +124,11 @@ class TestBigchainApi(object): with pytest.raises(DoubleSpend): b.get_spent(tx.id, 0) + @pytest.mark.genesis def test_get_block_status_for_tx_with_double_spend(self, b, monkeypatch): from bigchaindb.common.exceptions import DoubleSpend from bigchaindb.models import Transaction - b.create_genesis_block() - tx = Transaction.create([b.me], [([b.me], 1)]) tx = tx.sign([b.me_private]) @@ -151,19 +149,20 @@ class TestBigchainApi(object): with pytest.raises(DoubleSpend): b.get_blocks_status_containing_tx(tx.id) + @pytest.mark.genesis def test_get_transaction_in_invalid_and_valid_block(self, monkeypatch, b): from bigchaindb.models import Transaction - b.create_genesis_block() - monkeypatch.setattr('time.time', lambda: 1) - tx1 = Transaction.create([b.me], [([b.me], 1)]) + tx1 = Transaction.create([b.me], [([b.me], 1)], + metadata={'msg': random.random()}) tx1 = tx1.sign([b.me_private]) block1 = b.create_block([tx1]) b.write_block(block1) monkeypatch.setattr('time.time', lambda: 2222222222) - tx2 = Transaction.create([b.me], [([b.me], 1)]) + tx2 = Transaction.create([b.me], [([b.me], 1)], + metadata={'msg': random.random()}) tx2 = tx2.sign([b.me_private]) block2 = b.create_block([tx2]) b.write_block(block2) @@ -186,7 +185,8 @@ class TestBigchainApi(object): input_tx = b.get_owned_ids(user_pk).pop() input_tx = b.get_transaction(input_tx.txid) inputs = input_tx.to_inputs() - tx = Transaction.transfer(inputs, [([user_pk], 1)], input_tx.asset) + tx = Transaction.transfer(inputs, [([user_pk], 1)], + asset_id=input_tx.id) tx = tx.sign([user_sk]) response = b.write_transaction(tx) @@ -204,7 +204,8 @@ class TestBigchainApi(object): input_tx = b.get_owned_ids(user_pk).pop() input_tx = b.get_transaction(input_tx.txid) inputs = input_tx.to_inputs() - tx = Transaction.transfer(inputs, [([user_pk], 1)], input_tx.asset) + tx = Transaction.transfer(inputs, [([user_pk], 1)], + asset_id=input_tx.id) tx = tx.sign([user_sk]) b.write_transaction(tx) @@ -224,7 +225,8 @@ class TestBigchainApi(object): input_tx = b.get_owned_ids(user_pk).pop() input_tx = b.get_transaction(input_tx.txid) inputs = input_tx.to_inputs() - tx = Transaction.transfer(inputs, [([user_pk], 1)], input_tx.asset) + tx = Transaction.transfer(inputs, [([user_pk], 1)], + asset_id=input_tx.id) tx = tx.sign([user_sk]) # There's no need to b.write_transaction(tx) to the backlog @@ -248,7 +250,8 @@ class TestBigchainApi(object): input_tx = b.get_owned_ids(user_pk).pop() input_tx = b.get_transaction(input_tx.txid) inputs = input_tx.to_inputs() - tx = Transaction.transfer(inputs, [([user_pk], 1)], input_tx.asset) + tx = Transaction.transfer(inputs, [([user_pk], 1)], + asset_id=input_tx.id) tx = tx.sign([user_sk]) # Make sure there's a copy of tx in the backlog @@ -278,11 +281,10 @@ class TestBigchainApi(object): assert block['block']['transactions'][0]['operation'] == 'GENESIS' assert block['block']['transactions'][0]['inputs'][0]['fulfills'] is None + @pytest.mark.genesis def test_create_genesis_block_fails_if_table_not_empty(self, b): from bigchaindb.common.exceptions import GenesisBlockAlreadyExistsError - b.create_genesis_block() - with pytest.raises(GenesisBlockAlreadyExistsError): b.create_genesis_block() @@ -341,21 +343,22 @@ class TestBigchainApi(object): block, status = b.get_block(new_block.id, include_status=True) assert status == b.BLOCK_UNDECIDED + @pytest.mark.genesis def test_get_last_voted_block_returns_genesis_if_no_votes_has_been_casted(self, b): from bigchaindb.models import Block from bigchaindb.backend import query - b.create_genesis_block() genesis = query.get_genesis_block(b.connection) genesis = Block.from_dict(genesis) gb = b.get_last_voted_block() assert gb == genesis assert b.validate_block(gb) == gb - def test_get_last_voted_block_returns_the_correct_block_same_timestamp(self, b, monkeypatch): - genesis = b.create_genesis_block() - - assert b.get_last_voted_block() == genesis + def test_get_last_voted_block_returns_the_correct_block_same_timestamp(self, + b, + monkeypatch, + genesis_block): + assert b.get_last_voted_block() == genesis_block monkeypatch.setattr('time.time', lambda: 1) block_1 = dummy_block() @@ -379,10 +382,11 @@ class TestBigchainApi(object): b.write_vote(b.vote(block_3.id, b.get_last_voted_block().id, True)) assert b.get_last_voted_block().id == block_3.id - def test_get_last_voted_block_returns_the_correct_block_different_timestamps(self, b, monkeypatch): - genesis = b.create_genesis_block() - - assert b.get_last_voted_block() == genesis + def test_get_last_voted_block_returns_the_correct_block_different_timestamps(self, + b, + monkeypatch, + genesis_block): + assert b.get_last_voted_block() == genesis_block monkeypatch.setattr('time.time', lambda: 1) block_1 = dummy_block() @@ -408,28 +412,27 @@ class TestBigchainApi(object): b.write_vote(b.vote(block_3.id, b.get_last_voted_block().id, True)) assert b.get_last_voted_block().id == block_3.id - def test_no_vote_written_if_block_already_has_vote(self, b): + def test_no_vote_written_if_block_already_has_vote(self, b, genesis_block): from bigchaindb.models import Block - genesis = b.create_genesis_block() block_1 = dummy_block() b.write_block(block_1) - b.write_vote(b.vote(block_1.id, genesis.id, True)) + b.write_vote(b.vote(block_1.id, genesis_block.id, True)) retrieved_block_1 = b.get_block(block_1.id) retrieved_block_1 = Block.from_dict(retrieved_block_1) # try to vote again on the retrieved block, should do nothing - b.write_vote(b.vote(retrieved_block_1.id, genesis.id, True)) + b.write_vote(b.vote(retrieved_block_1.id, genesis_block.id, True)) retrieved_block_2 = b.get_block(block_1.id) retrieved_block_2 = Block.from_dict(retrieved_block_2) assert retrieved_block_1 == retrieved_block_2 + @pytest.mark.genesis def test_more_votes_than_voters(self, b): from bigchaindb.common.exceptions import MultipleVotesError - b.create_genesis_block() block_1 = dummy_block() b.write_block(block_1) # insert duplicate votes @@ -444,15 +447,14 @@ class TestBigchainApi(object): assert excinfo.value.args[0] == 'Block {block_id} has {n_votes} votes cast, but only {n_voters} voters'\ .format(block_id=block_1.id, n_votes=str(2), n_voters=str(1)) - def test_multiple_votes_single_node(self, b): + def test_multiple_votes_single_node(self, b, genesis_block): from bigchaindb.common.exceptions import MultipleVotesError - genesis = b.create_genesis_block() block_1 = dummy_block() b.write_block(block_1) # insert duplicate votes for i in range(2): - b.write_vote(b.vote(block_1.id, genesis.id, True)) + b.write_vote(b.vote(block_1.id, genesis_block.id, True)) with pytest.raises(MultipleVotesError) as excinfo: b.block_election_status(block_1.id, block_1.voters) @@ -464,10 +466,10 @@ class TestBigchainApi(object): assert excinfo.value.args[0] == 'Block {block_id} has {n_votes} votes from public key {me}'\ .format(block_id=block_1.id, n_votes=str(2), me=b.me) + @pytest.mark.genesis def test_improper_vote_error(selfs, b): from bigchaindb.common.exceptions import ImproperVoteError - b.create_genesis_block() block_1 = dummy_block() b.write_block(block_1) vote_1 = b.vote(block_1.id, b.get_last_voted_block().id, True) @@ -487,7 +489,8 @@ class TestBigchainApi(object): input_tx = b.get_owned_ids(user_pk).pop() input_tx = b.get_transaction(input_tx.txid) inputs = input_tx.to_inputs() - tx = Transaction.transfer(inputs, [([user_pk], 1)], input_tx.asset) + tx = Transaction.transfer(inputs, [([user_pk], 1)], + asset_id=input_tx.id) tx = tx.sign([user_sk]) b.write_transaction(tx) @@ -512,7 +515,9 @@ class TestBigchainApi(object): input_tx = b.get_owned_ids(user_pk).pop() input_tx = b.get_transaction(input_tx.txid) inputs = input_tx.to_inputs() - tx = Transaction.transfer(inputs, [([user_pk], 1)], input_tx.asset) + tx = Transaction.transfer(inputs, [([user_pk], 1)], + asset_id=input_tx.id, + metadata={'msg': random.random()}) tx = tx.sign([user_sk]) b.write_transaction(tx) @@ -527,8 +532,7 @@ class TestBigchainApi(object): def test_non_create_input_not_found(self, b, user_pk): from cryptoconditions import Ed25519Fulfillment from bigchaindb.common.exceptions import TransactionDoesNotExist - from bigchaindb.common.transaction import (Input, Asset, - TransactionLink) + from bigchaindb.common.transaction import Input, TransactionLink from bigchaindb.models import Transaction from bigchaindb import Bigchain @@ -536,7 +540,8 @@ class TestBigchainApi(object): input = Input(Ed25519Fulfillment(public_key=user_pk), [user_pk], TransactionLink('somethingsomething', 0)) - tx = Transaction.transfer([input], [([user_pk], 1)], Asset()) + tx = Transaction.transfer([input], [([user_pk], 1)], + asset_id='mock_asset_link') with pytest.raises(TransactionDoesNotExist): tx.validate(Bigchain()) @@ -546,8 +551,9 @@ class TestBigchainApi(object): from bigchaindb.models import Transaction for _ in range(4): - tx = Transaction.create([b.me], - [([user_pk], 1)]).sign([b.me_private]) + tx = Transaction.create([b.me], [([user_pk], 1)], + metadata={'msg': random.random()}) \ + .sign([b.me_private]) b.write_transaction(tx) assert query.count_backlog(b.connection) == 4 @@ -572,7 +578,7 @@ class TestTransactionValidation(object): assert excinfo.value.args[0] == 'Only `CREATE` transactions can have null inputs' - @pytest.mark.usefixtures('setup_database') + def test_non_create_input_not_found(self, b, user_pk, signed_transfer_tx): from bigchaindb.common.exceptions import TransactionDoesNotExist from bigchaindb.common.transaction import TransactionLink @@ -592,7 +598,7 @@ class TestTransactionValidation(object): sk, pk = generate_key_pair() tx = Transaction.create([pk], [([user_pk], 1)]) tx.operation = 'TRANSFER' - tx.asset = input_transaction.asset + tx.asset = {'id': input_transaction.id} tx.inputs[0].fulfills = input_tx with pytest.raises(InvalidSignature): @@ -635,7 +641,7 @@ class TestTransactionValidation(object): input_tx = b.get_transaction(input_tx.txid) inputs = input_tx.to_inputs() transfer_tx = Transaction.transfer(inputs, [([user_pk], 1)], - input_tx.asset) + asset_id=input_tx.id) transfer_tx = transfer_tx.sign([user_sk]) assert transfer_tx == b.validate_transaction(transfer_tx) @@ -660,7 +666,7 @@ class TestTransactionValidation(object): # create a transaction that's valid but not in a voted valid block transfer_tx = Transaction.transfer(inputs, [([user_pk], 1)], - input_tx.asset) + asset_id=input_tx.id) transfer_tx = transfer_tx.sign([user_sk]) assert transfer_tx == b.validate_transaction(transfer_tx) @@ -672,7 +678,7 @@ class TestTransactionValidation(object): # create transaction with the undecided input tx_invalid = Transaction.transfer(transfer_tx.to_inputs(), [([user_pk], 1)], - transfer_tx.asset) + asset_id=transfer_tx.asset['id']) tx_invalid = tx_invalid.sign([user_sk]) with pytest.raises(TransactionNotInValidBlock): @@ -723,7 +729,7 @@ class TestBlockValidation(object): assert excinfo.value.args[0] == 'owner_before `a` does not own the input `{}`'.format(valid_input) - @pytest.mark.usefixtures('setup_database') + def test_invalid_signature(self, b): from bigchaindb.common.exceptions import InvalidSignature from bigchaindb.common import crypto @@ -738,7 +744,7 @@ class TestBlockValidation(object): with pytest.raises(InvalidSignature): b.validate_block(block) - @pytest.mark.usefixtures('setup_database') + def test_invalid_node_pubkey(self, b): from bigchaindb.common.exceptions import OperationError from bigchaindb.common import crypto @@ -772,7 +778,8 @@ class TestMultipleInputs(object): tx_link = b.get_owned_ids(user_pk).pop() input_tx = b.get_transaction(tx_link.txid) inputs = input_tx.to_inputs() - tx = Transaction.transfer(inputs, [([user2_pk], 1)], input_tx.asset) + tx = Transaction.transfer(inputs, [([user2_pk], 1)], + asset_id=input_tx.id) tx = tx.sign([user_sk]) # validate transaction @@ -794,7 +801,8 @@ class TestMultipleInputs(object): tx_link = owned_inputs.pop() input_tx = b.get_transaction(tx_link.txid) tx = Transaction.transfer(input_tx.to_inputs(), - [([user2_pk, user3_pk], 1)], input_tx.asset) + [([user2_pk, user3_pk], 1)], + asset_id=input_tx.id) tx = tx.sign([user_sk]) assert b.is_valid_transaction(tx) == tx @@ -825,7 +833,7 @@ class TestMultipleInputs(object): inputs = input_tx.to_inputs() transfer_tx = Transaction.transfer(inputs, [([user3_pk], 1)], - input_tx.asset) + asset_id=input_tx.id) transfer_tx = transfer_tx.sign([user_sk, user2_sk]) # validate transaction @@ -858,14 +866,14 @@ class TestMultipleInputs(object): tx_input = b.get_transaction(tx_link.txid) tx = Transaction.transfer(tx_input.to_inputs(), - [([user3_pk, user4_pk], 1)], tx_input.asset) + [([user3_pk, user4_pk], 1)], + asset_id=tx_input.id) tx = tx.sign([user_sk, user2_sk]) assert b.is_valid_transaction(tx) == tx assert len(tx.inputs) == 1 assert len(tx.outputs) == 1 - @pytest.mark.usefixtures('setup_database') def test_get_owned_ids_single_tx_single_output(self, b, user_sk, user_pk): from bigchaindb.common import crypto from bigchaindb.common.transaction import TransactionLink @@ -883,7 +891,8 @@ class TestMultipleInputs(object): assert owned_inputs_user1 == [TransactionLink(tx.id, 0)] assert owned_inputs_user2 == [] - tx = Transaction.transfer(tx.to_inputs(), [([user2_pk], 1)], tx.asset) + tx = Transaction.transfer(tx.to_inputs(), [([user2_pk], 1)], + asset_id=tx.id) tx = tx.sign([user_sk]) block = b.create_block([tx]) b.write_block(block) @@ -893,15 +902,14 @@ class TestMultipleInputs(object): assert owned_inputs_user1 == [] assert owned_inputs_user2 == [TransactionLink(tx.id, 0)] - @pytest.mark.usefixtures('setup_database') def test_get_owned_ids_single_tx_single_output_invalid_block(self, b, user_sk, - user_pk): + user_pk, + genesis_block): from bigchaindb.common import crypto from bigchaindb.common.transaction import TransactionLink from bigchaindb.models import Transaction - genesis = b.create_genesis_block() user2_sk, user2_pk = crypto.generate_key_pair() tx = Transaction.create([b.me], [([user_pk], 1)]) @@ -910,7 +918,7 @@ class TestMultipleInputs(object): b.write_block(block) # vote the block VALID - vote = b.vote(block.id, genesis.id, True) + vote = b.vote(block.id, genesis_block.id, True) b.write_vote(vote) owned_inputs_user1 = b.get_owned_ids(user_pk) @@ -921,7 +929,7 @@ class TestMultipleInputs(object): # NOTE: The transaction itself is valid, still will mark the block # as invalid to mock the behavior. tx_invalid = Transaction.transfer(tx.to_inputs(), [([user2_pk], 1)], - tx.asset) + asset_id=tx.id) tx_invalid = tx_invalid.sign([user_sk]) block = b.create_block([tx_invalid]) b.write_block(block) @@ -937,20 +945,16 @@ class TestMultipleInputs(object): assert owned_inputs_user1 == [TransactionLink(tx.id, 0)] assert owned_inputs_user2 == [] - @pytest.mark.usefixtures('setup_database') def test_get_owned_ids_single_tx_multiple_outputs(self, b, user_sk, user_pk): from bigchaindb.common import crypto - from bigchaindb.common.transaction import TransactionLink, Asset + from bigchaindb.common.transaction import TransactionLink from bigchaindb.models import Transaction user2_sk, user2_pk = crypto.generate_key_pair() # create divisible asset - asset = Asset(divisible=True) - tx_create = Transaction.create([b.me], - [([user_pk], 1), ([user_pk], 1)], - asset=asset) + tx_create = Transaction.create([b.me], [([user_pk], 1), ([user_pk], 1)]) tx_create_signed = tx_create.sign([b.me_private]) block = b.create_block([tx_create_signed]) b.write_block(block) @@ -967,7 +971,7 @@ class TestMultipleInputs(object): # transfer divisible asset divided in two outputs tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user2_pk], 1), ([user2_pk], 1)], - asset=tx_create.asset) + asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) block = b.create_block([tx_transfer_signed]) b.write_block(block) @@ -978,7 +982,6 @@ class TestMultipleInputs(object): assert owned_inputs_user2 == [TransactionLink(tx_transfer.id, 0), TransactionLink(tx_transfer.id, 1)] - @pytest.mark.usefixtures('setup_database') def test_get_owned_ids_multiple_owners(self, b, user_sk, user_pk): from bigchaindb.common import crypto from bigchaindb.common.transaction import TransactionLink @@ -999,7 +1002,8 @@ class TestMultipleInputs(object): assert owned_inputs_user1 == owned_inputs_user2 assert owned_inputs_user1 == expected_owned_inputs_user1 - tx = Transaction.transfer(tx.to_inputs(), [([user3_pk], 1)], tx.asset) + tx = Transaction.transfer(tx.to_inputs(), [([user3_pk], 1)], + asset_id=tx.id) tx = tx.sign([user_sk, user2_sk]) block = b.create_block([tx]) b.write_block(block) @@ -1009,7 +1013,6 @@ class TestMultipleInputs(object): assert owned_inputs_user1 == owned_inputs_user2 assert owned_inputs_user1 == [] - @pytest.mark.usefixtures('setup_database') def test_get_spent_single_tx_single_output(self, b, user_sk, user_pk): from bigchaindb.common import crypto from bigchaindb.models import Transaction @@ -1030,7 +1033,8 @@ class TestMultipleInputs(object): assert spent_inputs_user1 is None # create a transaction and block - tx = Transaction.transfer(tx.to_inputs(), [([user2_pk], 1)], tx.asset) + tx = Transaction.transfer(tx.to_inputs(), [([user2_pk], 1)], + asset_id=tx.id) tx = tx.sign([user_sk]) block = b.create_block([tx]) b.write_block(block) @@ -1038,13 +1042,13 @@ class TestMultipleInputs(object): spent_inputs_user1 = b.get_spent(input_txid, input_idx) assert spent_inputs_user1 == tx - @pytest.mark.usefixtures('setup_database') - def test_get_spent_single_tx_single_output_invalid_block(self, b, user_sk, user_pk): + def test_get_spent_single_tx_single_output_invalid_block(self, b, + user_sk, + user_pk, + genesis_block): from bigchaindb.common import crypto from bigchaindb.models import Transaction - genesis = b.create_genesis_block() - # create a new users user2_sk, user2_pk = crypto.generate_key_pair() @@ -1054,7 +1058,7 @@ class TestMultipleInputs(object): b.write_block(block) # vote the block VALID - vote = b.vote(block.id, genesis.id, True) + vote = b.vote(block.id, genesis_block.id, True) b.write_vote(vote) owned_inputs_user1 = b.get_owned_ids(user_pk).pop() @@ -1066,7 +1070,8 @@ class TestMultipleInputs(object): assert spent_inputs_user1 is None # create a transaction and block - tx = Transaction.transfer(tx.to_inputs(), [([user2_pk], 1)], tx.asset) + tx = Transaction.transfer(tx.to_inputs(), [([user2_pk], 1)], + asset_id=tx.id) tx = tx.sign([user_sk]) block = b.create_block([tx]) b.write_block(block) @@ -1081,22 +1086,18 @@ class TestMultipleInputs(object): # Now there should be no spents (the block is invalid) assert spent_inputs_user1 is None - @pytest.mark.usefixtures('setup_database') def test_get_spent_single_tx_multiple_outputs(self, b, user_sk, user_pk): from bigchaindb.common import crypto from bigchaindb.models import Transaction - from bigchaindb.common.transaction import Asset # create a new users user2_sk, user2_pk = crypto.generate_key_pair() # create a divisible asset with 3 outputs - asset = Asset(divisible=True) tx_create = Transaction.create([b.me], [([user_pk], 1), ([user_pk], 1), - ([user_pk], 1)], - asset=asset) + ([user_pk], 1)]) tx_create_signed = tx_create.sign([b.me_private]) block = b.create_block([tx_create_signed]) b.write_block(block) @@ -1110,7 +1111,7 @@ class TestMultipleInputs(object): # transfer the first 2 inputs tx_transfer = Transaction.transfer(tx_create.to_inputs()[:2], [([user2_pk], 1), ([user2_pk], 1)], - asset=tx_create.asset) + asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) block = b.create_block([tx_transfer_signed]) b.write_block(block) @@ -1124,9 +1125,7 @@ class TestMultipleInputs(object): # spendable by BigchainDB assert b.get_spent(tx_create.to_inputs()[2].fulfills.txid, 2) is None - @pytest.mark.usefixtures('setup_database') def test_get_spent_multiple_owners(self, b, user_sk, user_pk): - import random from bigchaindb.common import crypto from bigchaindb.models import Transaction @@ -1151,7 +1150,8 @@ class TestMultipleInputs(object): # create a transaction tx = Transaction.transfer(transactions[0].to_inputs(), - [([user3_pk], 1)], transactions[0].asset) + [([user3_pk], 1)], + asset_id=transactions[0].id) tx = tx.sign([user_sk, user2_sk]) block = b.create_block([tx]) b.write_block(block) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 1faba9a6..574197db 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -3,8 +3,7 @@ from bigchaindb.pipelines import block, election, vote, stale @pytest.fixture -def processes(b, setup_database): - b.create_genesis_block() +def processes(genesis_block): block_maker = block.start() voter = vote.start() election_runner = election.start() diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index 480c20f8..70781096 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -2,8 +2,9 @@ import time import pytest +pytestmark = [pytest.mark.bdb, pytest.mark.usefixtures('processes')] + -@pytest.mark.usefixtures('processes') def test_fast_double_create(b, user_pk): from bigchaindb.models import Transaction from bigchaindb.backend.query import count_blocks @@ -25,7 +26,6 @@ def test_fast_double_create(b, user_pk): assert count_blocks(b.connection) == 2 -@pytest.mark.usefixtures('processes') def test_double_create(b, user_pk): from bigchaindb.models import Transaction from bigchaindb.backend.query import count_blocks diff --git a/tests/pipelines/test_block_creation.py b/tests/pipelines/test_block_creation.py index 85ca1d0d..18d291ea 100644 --- a/tests/pipelines/test_block_creation.py +++ b/tests/pipelines/test_block_creation.py @@ -1,3 +1,4 @@ +import random import time from unittest.mock import patch @@ -26,7 +27,7 @@ def test_filter_by_assignee(b, signed_create_tx): assert block_maker.filter_tx(tx) is None -@pytest.mark.usefixtures('setup_database') +@pytest.mark.bdb def test_validate_transaction(b, create_tx): from bigchaindb.pipelines.block import BlockPipeline @@ -44,8 +45,9 @@ def test_create_block(b, user_pk): block_maker = BlockPipeline() - for i in range(100): - tx = Transaction.create([b.me], [([user_pk], 1)]) + for _ in range(100): + tx = Transaction.create([b.me], [([user_pk], 1)], + metadata={'msg': random.random()}) tx = tx.sign([b.me_private]) block_maker.create(tx) @@ -55,7 +57,7 @@ def test_create_block(b, user_pk): assert len(block_doc.transactions) == 100 -@pytest.mark.usefixtures('setup_database') +@pytest.mark.bdb def test_write_block(b, user_pk): from bigchaindb.models import Block, Transaction from bigchaindb.pipelines.block import BlockPipeline @@ -63,8 +65,9 @@ def test_write_block(b, user_pk): block_maker = BlockPipeline() txs = [] - for i in range(100): - tx = Transaction.create([b.me], [([user_pk], 1)]) + for _ in range(100): + tx = Transaction.create([b.me], [([user_pk], 1)], + metadata={'msg': random.random()}) tx = tx.sign([b.me_private]) txs.append(tx) @@ -76,15 +79,16 @@ def test_write_block(b, user_pk): assert expected == block_doc -@pytest.mark.usefixtures('setup_database') +@pytest.mark.bdb def test_duplicate_transaction(b, user_pk): from bigchaindb.models import Transaction from bigchaindb.pipelines import block block_maker = block.BlockPipeline() txs = [] - for i in range(10): - tx = Transaction.create([b.me], [([user_pk], 1)]) + for _ in range(10): + tx = Transaction.create([b.me], [([user_pk], 1)], + metadata={'msg': random.random()}) tx = tx.sign([b.me_private]) txs.append(tx) @@ -108,13 +112,14 @@ def test_duplicate_transaction(b, user_pk): assert status != b.TX_IN_BACKLOG -@pytest.mark.usefixtures('setup_database') +@pytest.mark.bdb def test_delete_tx(b, user_pk): from bigchaindb.models import Transaction from bigchaindb.pipelines.block import BlockPipeline block_maker = BlockPipeline() for i in range(100): - tx = Transaction.create([b.me], [([user_pk], 1)]) + tx = Transaction.create([b.me], [([user_pk], 1)], + metadata={'msg': random.random()}) tx = tx.sign([b.me_private]) block_maker.create(tx) # make sure the tx appears in the backlog @@ -137,7 +142,7 @@ def test_delete_tx(b, user_pk): @patch('bigchaindb.pipelines.block.create_pipeline') -@pytest.mark.usefixtures('setup_database') +@pytest.mark.bdb def test_start(create_pipeline): from bigchaindb.pipelines import block @@ -148,12 +153,10 @@ def test_start(create_pipeline): assert pipeline == create_pipeline.return_value -@pytest.mark.usefixtures('setup_database') +@pytest.mark.bdb def test_full_pipeline(b, user_pk): - import random - from bigchaindb.backend import query from bigchaindb.models import Block, Transaction - from bigchaindb.pipelines.block import create_pipeline, get_changefeed + from bigchaindb.pipelines.block import create_pipeline outpipe = Pipe() @@ -166,7 +169,7 @@ def test_full_pipeline(b, user_pk): number_assigned_to_others = 0 for i in range(100): tx = Transaction.create([b.me], [([user_pk], 1)], - {'msg': random.random()}) + metadata={'msg': random.random()}) tx = tx.sign([b.me_private]) tx = tx.to_dict() diff --git a/tests/pipelines/test_election.py b/tests/pipelines/test_election.py index a4a7d876..28141dff 100644 --- a/tests/pipelines/test_election.py +++ b/tests/pipelines/test_election.py @@ -10,7 +10,7 @@ from bigchaindb import Bigchain from bigchaindb.pipelines import election -@pytest.mark.usefixtures('setup_database') +@pytest.mark.bdb def test_check_for_quorum_invalid(b, user_pk): from bigchaindb.models import Transaction @@ -44,7 +44,7 @@ def test_check_for_quorum_invalid(b, user_pk): assert e.check_for_quorum(votes[-1]) == test_block -@pytest.mark.usefixtures('setup_database') +@pytest.mark.bdb def test_check_for_quorum_invalid_prev_node(b, user_pk): from bigchaindb.models import Transaction e = election.Election() @@ -79,7 +79,7 @@ def test_check_for_quorum_invalid_prev_node(b, user_pk): assert e.check_for_quorum(votes[-1]) == test_block -@pytest.mark.usefixtures('setup_database') +@pytest.mark.bdb def test_check_for_quorum_valid(b, user_pk): from bigchaindb.models import Transaction @@ -112,7 +112,7 @@ def test_check_for_quorum_valid(b, user_pk): assert e.check_for_quorum(votes[-1]) is None -@pytest.mark.usefixtures('setup_database') +@pytest.mark.bdb def test_check_requeue_transaction(b, user_pk): from bigchaindb.models import Transaction @@ -140,7 +140,7 @@ def test_start(mock_start): mock_start.assert_called_with() -@pytest.mark.usefixtures('setup_database') +@pytest.mark.bdb def test_full_pipeline(b, user_pk): import random from bigchaindb.backend import query diff --git a/tests/pipelines/test_stale_monitor.py b/tests/pipelines/test_stale_monitor.py index a39447bf..e964e28c 100644 --- a/tests/pipelines/test_stale_monitor.py +++ b/tests/pipelines/test_stale_monitor.py @@ -1,14 +1,15 @@ +import os +import random from bigchaindb import Bigchain from bigchaindb.pipelines import stale from multipipes import Pipe, Pipeline from unittest.mock import patch from bigchaindb import config_utils -import os import pytest -@pytest.mark.usefixtures('setup_database') +@pytest.mark.bdb def test_get_stale(b, user_pk): from bigchaindb.models import Transaction tx = Transaction.create([b.me], [([user_pk], 1)]) @@ -25,7 +26,7 @@ def test_get_stale(b, user_pk): assert tx.to_dict() == _tx -@pytest.mark.usefixtures('setup_database') +@pytest.mark.bdb def test_reassign_transactions(b, user_pk): from bigchaindb.backend import query from bigchaindb.models import Transaction @@ -65,22 +66,15 @@ def test_reassign_transactions(b, user_pk): assert tx['assignee'] != 'lol' -@pytest.mark.usefixtures('setup_database') +@pytest.mark.bdb def test_full_pipeline(monkeypatch, user_pk): from bigchaindb.backend import query from bigchaindb.models import Transaction CONFIG = { - 'database': { - 'name': 'bigchain_test_{}'.format(os.getpid()) - }, - 'keypair': { - 'private': '31Lb1ZGKTyHnmVK3LUMrAUrPNfd4sE2YyBt3UA4A25aA', - 'public': '4XYfCbabAWVUCbjTmRTFEu2sc3dFEdkse4r6X498B1s8' - }, 'keyring': ['aaa', 'bbb'], 'backlog_reassign_delay': 0.01 } - config_utils.set_config(CONFIG) + config_utils.update_config(CONFIG) b = Bigchain() original_txs = {} @@ -89,7 +83,8 @@ def test_full_pipeline(monkeypatch, user_pk): monkeypatch.setattr('time.time', lambda: 1) for i in range(100): - tx = Transaction.create([b.me], [([user_pk], 1)]) + tx = Transaction.create([b.me], [([user_pk], 1)], + metadata={'msg': random.random()}) tx = tx.sign([b.me_private]) original_txc.append(tx.to_dict()) diff --git a/tests/pipelines/test_vote.py b/tests/pipelines/test_vote.py index 9f1cf091..2983b1db 100644 --- a/tests/pipelines/test_vote.py +++ b/tests/pipelines/test_vote.py @@ -1,3 +1,4 @@ +import random import time from unittest.mock import patch @@ -5,9 +6,11 @@ from multipipes import Pipe, Pipeline import pytest +# TODO: dummy_tx and dummy_block could be fixtures def dummy_tx(b): from bigchaindb.models import Transaction - tx = Transaction.create([b.me], [([b.me], 1)]) + tx = Transaction.create([b.me], [([b.me], 1)], + metadata={'msg': random.random()}) tx = tx.sign([b.me_private]) return tx @@ -59,11 +62,10 @@ def test_vote_creation_invalid(b): vote['signature']) is True -@pytest.mark.usefixtures('setup_database') +@pytest.mark.genesis def test_vote_ungroup_returns_a_set_of_results(b): from bigchaindb.pipelines import vote - b.create_genesis_block() block = dummy_block(b) vote_obj = vote.Vote() txs = list(vote_obj.ungroup(block.id, block.transactions)) @@ -71,11 +73,10 @@ def test_vote_ungroup_returns_a_set_of_results(b): assert len(txs) == 10 -@pytest.mark.usefixtures('setup_database') +@pytest.mark.genesis def test_vote_validate_block(b): from bigchaindb.pipelines import vote - b.create_genesis_block() tx = dummy_tx(b) block = b.create_block([tx]) @@ -96,11 +97,10 @@ def test_vote_validate_block(b): assert tx1 == tx2 -@pytest.mark.usefixtures('setup_database') +@pytest.mark.genesis def test_validate_block_with_invalid_id(b): from bigchaindb.pipelines import vote - b.create_genesis_block() tx = dummy_tx(b) block = b.create_block([tx]).to_dict() block['id'] = 'an invalid id' @@ -111,11 +111,10 @@ def test_validate_block_with_invalid_id(b): assert invalid_dummy_tx == [vote_obj.invalid_dummy_tx] -@pytest.mark.usefixtures('setup_database') +@pytest.mark.genesis def test_validate_block_with_invalid_signature(b): from bigchaindb.pipelines import vote - b.create_genesis_block() tx = dummy_tx(b) block = b.create_block([tx]).to_dict() block['signature'] = 'an invalid signature' @@ -126,12 +125,11 @@ def test_validate_block_with_invalid_signature(b): assert invalid_dummy_tx == [vote_obj.invalid_dummy_tx] -@pytest.mark.usefixtures('setup_database') +@pytest.mark.genesis def test_vote_validate_transaction(b): from bigchaindb.pipelines import vote from bigchaindb.models import Transaction - b.create_genesis_block() tx = dummy_tx(b) vote_obj = vote.Vote() validation = vote_obj.validate_tx(tx, 123, 1) @@ -143,11 +141,10 @@ def test_vote_validate_transaction(b): assert validation == (False, 456, 10) -@pytest.mark.usefixtures('setup_database') +@pytest.mark.genesis def test_vote_accumulates_transactions(b): from bigchaindb.pipelines import vote - b.create_genesis_block() vote_obj = vote.Vote() for _ in range(10): @@ -162,14 +159,13 @@ def test_vote_accumulates_transactions(b): assert validation == (False, 456, 10) -@pytest.mark.usefixtures('setup_database') -def test_valid_block_voting_sequential(b, monkeypatch): +@pytest.mark.bdb +def test_valid_block_voting_sequential(b, genesis_block, monkeypatch): from bigchaindb.backend import query from bigchaindb.common import crypto, util from bigchaindb.pipelines import vote monkeypatch.setattr('time.time', lambda: 1111111111) - genesis = b.create_genesis_block() vote_obj = vote.Vote() block = dummy_block(b) @@ -181,7 +177,7 @@ def test_valid_block_voting_sequential(b, monkeypatch): vote_doc = vote_rs.next() assert vote_doc['vote'] == {'voting_for_block': block.id, - 'previous_block': genesis.id, + 'previous_block': genesis_block.id, 'is_block_valid': True, 'invalid_reason': None, 'timestamp': '1111111111'} @@ -192,8 +188,8 @@ def test_valid_block_voting_sequential(b, monkeypatch): vote_doc['signature']) is True -@pytest.mark.usefixtures('setup_database') -def test_valid_block_voting_multiprocessing(b, monkeypatch): +@pytest.mark.bdb +def test_valid_block_voting_multiprocessing(b, genesis_block, monkeypatch): from bigchaindb.backend import query from bigchaindb.common import crypto, util from bigchaindb.pipelines import vote @@ -202,7 +198,6 @@ def test_valid_block_voting_multiprocessing(b, monkeypatch): outpipe = Pipe() monkeypatch.setattr('time.time', lambda: 1111111111) - genesis = b.create_genesis_block() vote_pipeline = vote.create_pipeline() vote_pipeline.setup(indata=inpipe, outdata=outpipe) @@ -217,7 +212,7 @@ def test_valid_block_voting_multiprocessing(b, monkeypatch): vote_doc = vote_rs.next() assert vote_out['vote'] == vote_doc['vote'] assert vote_doc['vote'] == {'voting_for_block': block.id, - 'previous_block': genesis.id, + 'previous_block': genesis_block.id, 'is_block_valid': True, 'invalid_reason': None, 'timestamp': '1111111111'} @@ -228,15 +223,15 @@ def test_valid_block_voting_multiprocessing(b, monkeypatch): vote_doc['signature']) is True -@pytest.mark.usefixtures('setup_database') -def test_valid_block_voting_with_create_transaction(b, monkeypatch): +@pytest.mark.bdb +def test_valid_block_voting_with_create_transaction(b, + genesis_block, + monkeypatch): from bigchaindb.backend import query from bigchaindb.common import crypto, util from bigchaindb.models import Transaction from bigchaindb.pipelines import vote - genesis = b.create_genesis_block() - # create a `CREATE` transaction test_user_priv, test_user_pub = crypto.generate_key_pair() tx = Transaction.create([b.me], [([test_user_pub], 1)]) @@ -260,7 +255,7 @@ def test_valid_block_voting_with_create_transaction(b, monkeypatch): vote_doc = vote_rs.next() assert vote_out['vote'] == vote_doc['vote'] assert vote_doc['vote'] == {'voting_for_block': block.id, - 'previous_block': genesis.id, + 'previous_block': genesis_block.id, 'is_block_valid': True, 'invalid_reason': None, 'timestamp': '1111111111'} @@ -271,15 +266,14 @@ def test_valid_block_voting_with_create_transaction(b, monkeypatch): vote_doc['signature']) is True -@pytest.mark.usefixtures('setup_database') -def test_valid_block_voting_with_transfer_transactions(monkeypatch, b): +@pytest.mark.bdb +def test_valid_block_voting_with_transfer_transactions(monkeypatch, + b, genesis_block): from bigchaindb.backend import query from bigchaindb.common import crypto, util from bigchaindb.models import Transaction from bigchaindb.pipelines import vote - genesis = b.create_genesis_block() - # create a `CREATE` transaction test_user_priv, test_user_pub = crypto.generate_key_pair() tx = Transaction.create([b.me], [([test_user_pub], 1)]) @@ -292,7 +286,7 @@ def test_valid_block_voting_with_transfer_transactions(monkeypatch, b): # create a `TRANSFER` transaction test_user2_priv, test_user2_pub = crypto.generate_key_pair() tx2 = Transaction.transfer(tx.to_inputs(), [([test_user2_pub], 1)], - tx.asset) + asset_id=tx.id) tx2 = tx2.sign([test_user_priv]) monkeypatch.setattr('time.time', lambda: 2222222222) @@ -317,7 +311,7 @@ def test_valid_block_voting_with_transfer_transactions(monkeypatch, b): vote_doc = vote_rs.next() assert vote_out['vote'] == vote_doc['vote'] assert vote_doc['vote'] == {'voting_for_block': block.id, - 'previous_block': genesis.id, + 'previous_block': genesis_block.id, 'is_block_valid': True, 'invalid_reason': None, 'timestamp': '2222222222'} @@ -342,8 +336,8 @@ def test_valid_block_voting_with_transfer_transactions(monkeypatch, b): vote2_doc['signature']) is True -@pytest.mark.usefixtures('setup_database') -def test_unsigned_tx_in_block_voting(monkeypatch, b, user_pk): +@pytest.mark.bdb +def test_unsigned_tx_in_block_voting(monkeypatch, b, user_pk, genesis_block): from bigchaindb.backend import query from bigchaindb.common import crypto, util from bigchaindb.models import Transaction @@ -353,7 +347,6 @@ def test_unsigned_tx_in_block_voting(monkeypatch, b, user_pk): outpipe = Pipe() monkeypatch.setattr('time.time', lambda: 1111111111) - genesis = b.create_genesis_block() vote_pipeline = vote.create_pipeline() vote_pipeline.setup(indata=inpipe, outdata=outpipe) @@ -370,7 +363,7 @@ def test_unsigned_tx_in_block_voting(monkeypatch, b, user_pk): vote_doc = vote_rs.next() assert vote_out['vote'] == vote_doc['vote'] assert vote_doc['vote'] == {'voting_for_block': block.id, - 'previous_block': genesis.id, + 'previous_block': genesis_block.id, 'is_block_valid': False, 'invalid_reason': None, 'timestamp': '1111111111'} @@ -381,8 +374,8 @@ def test_unsigned_tx_in_block_voting(monkeypatch, b, user_pk): vote_doc['signature']) is True -@pytest.mark.usefixtures('setup_database') -def test_invalid_id_tx_in_block_voting(monkeypatch, b, user_pk): +@pytest.mark.bdb +def test_invalid_id_tx_in_block_voting(monkeypatch, b, user_pk, genesis_block): from bigchaindb.backend import query from bigchaindb.common import crypto, util from bigchaindb.models import Transaction @@ -392,7 +385,6 @@ def test_invalid_id_tx_in_block_voting(monkeypatch, b, user_pk): outpipe = Pipe() monkeypatch.setattr('time.time', lambda: 1111111111) - genesis = b.create_genesis_block() vote_pipeline = vote.create_pipeline() vote_pipeline.setup(indata=inpipe, outdata=outpipe) @@ -411,7 +403,7 @@ def test_invalid_id_tx_in_block_voting(monkeypatch, b, user_pk): vote_doc = vote_rs.next() assert vote_out['vote'] == vote_doc['vote'] assert vote_doc['vote'] == {'voting_for_block': block['id'], - 'previous_block': genesis.id, + 'previous_block': genesis_block.id, 'is_block_valid': False, 'invalid_reason': None, 'timestamp': '1111111111'} @@ -422,8 +414,9 @@ def test_invalid_id_tx_in_block_voting(monkeypatch, b, user_pk): vote_doc['signature']) is True -@pytest.mark.usefixtures('setup_database') -def test_invalid_content_in_tx_in_block_voting(monkeypatch, b, user_pk): +@pytest.mark.bdb +def test_invalid_content_in_tx_in_block_voting(monkeypatch, b, + user_pk, genesis_block): from bigchaindb.backend import query from bigchaindb.common import crypto, util from bigchaindb.models import Transaction @@ -433,7 +426,6 @@ def test_invalid_content_in_tx_in_block_voting(monkeypatch, b, user_pk): outpipe = Pipe() monkeypatch.setattr('time.time', lambda: 1111111111) - genesis = b.create_genesis_block() vote_pipeline = vote.create_pipeline() vote_pipeline.setup(indata=inpipe, outdata=outpipe) @@ -452,7 +444,7 @@ def test_invalid_content_in_tx_in_block_voting(monkeypatch, b, user_pk): vote_doc = vote_rs.next() assert vote_out['vote'] == vote_doc['vote'] assert vote_doc['vote'] == {'voting_for_block': block['id'], - 'previous_block': genesis.id, + 'previous_block': genesis_block.id, 'is_block_valid': False, 'invalid_reason': None, 'timestamp': '1111111111'} @@ -463,8 +455,8 @@ def test_invalid_content_in_tx_in_block_voting(monkeypatch, b, user_pk): vote_doc['signature']) is True -@pytest.mark.usefixtures('setup_database') -def test_invalid_block_voting(monkeypatch, b, user_pk): +@pytest.mark.bdb +def test_invalid_block_voting(monkeypatch, b, user_pk, genesis_block): from bigchaindb.backend import query from bigchaindb.common import crypto, util from bigchaindb.pipelines import vote @@ -473,7 +465,6 @@ def test_invalid_block_voting(monkeypatch, b, user_pk): outpipe = Pipe() monkeypatch.setattr('time.time', lambda: 1111111111) - genesis = b.create_genesis_block() vote_pipeline = vote.create_pipeline() vote_pipeline.setup(indata=inpipe, outdata=outpipe) @@ -489,7 +480,7 @@ def test_invalid_block_voting(monkeypatch, b, user_pk): vote_doc = vote_rs.next() assert vote_out['vote'] == vote_doc['vote'] assert vote_doc['vote'] == {'voting_for_block': block['id'], - 'previous_block': genesis.id, + 'previous_block': genesis_block.id, 'is_block_valid': False, 'invalid_reason': None, 'timestamp': '1111111111'} @@ -500,7 +491,7 @@ def test_invalid_block_voting(monkeypatch, b, user_pk): vote_doc['signature']) is True -@pytest.mark.usefixtures('setup_database') +@pytest.mark.genesis def test_voter_considers_unvoted_blocks_when_single_node(monkeypatch, b): from bigchaindb.backend import query from bigchaindb.pipelines import vote @@ -508,7 +499,6 @@ def test_voter_considers_unvoted_blocks_when_single_node(monkeypatch, b): outpipe = Pipe() monkeypatch.setattr('time.time', lambda: 1111111111) - b.create_genesis_block() block_ids = [] # insert blocks in the database while the voter process is not listening @@ -549,7 +539,7 @@ def test_voter_considers_unvoted_blocks_when_single_node(monkeypatch, b): assert all(vote['node_pubkey'] == b.me for vote in votes) -@pytest.mark.usefixtures('setup_database') +@pytest.mark.genesis def test_voter_chains_blocks_with_the_previous_ones(monkeypatch, b): from bigchaindb.backend import query from bigchaindb.pipelines import vote @@ -557,7 +547,6 @@ def test_voter_chains_blocks_with_the_previous_ones(monkeypatch, b): outpipe = Pipe() monkeypatch.setattr('time.time', lambda: 1111111111) - b.create_genesis_block() block_ids = [] monkeypatch.setattr('time.time', lambda: 2222222222) @@ -591,7 +580,7 @@ def test_voter_chains_blocks_with_the_previous_ones(monkeypatch, b): {block['id'] for block in blocks}) -@pytest.mark.usefixtures('setup_database') +@pytest.mark.genesis def test_voter_checks_for_previous_vote(monkeypatch, b): from bigchaindb.backend import query from bigchaindb.pipelines import vote @@ -600,7 +589,6 @@ def test_voter_checks_for_previous_vote(monkeypatch, b): outpipe = Pipe() monkeypatch.setattr('time.time', lambda: 1111111111) - b.create_genesis_block() monkeypatch.setattr('time.time', lambda: 2222222222) block_1 = dummy_block(b) @@ -633,14 +621,11 @@ def test_voter_checks_for_previous_vote(monkeypatch, b): @patch.object(Pipeline, 'start') -@pytest.mark.usefixtures('setup_database') +@pytest.mark.genesis def test_start(mock_start, b): # TODO: `block.start` is just a wrapper around `vote.create_pipeline`, # that is tested by `test_full_pipeline`. # If anyone has better ideas on how to test this, please do a PR :) from bigchaindb.pipelines import vote - - b.create_genesis_block() - vote.start() mock_start.assert_called_with() diff --git a/tests/test_config_utils.py b/tests/test_config_utils.py index 548d3160..2a326147 100644 --- a/tests/test_config_utils.py +++ b/tests/test_config_utils.py @@ -9,19 +9,6 @@ import bigchaindb ORIGINAL_CONFIG = copy.deepcopy(bigchaindb._config) -@pytest.fixture -def ignore_local_config_file(monkeypatch): - """ - This fixture's purpose is to override the one under - :module:`tests/conftest.py` so that the original behaviour of - :func:`bigchaindb.config_utils.file_config` is restored, so that it can be - tested. - - """ - from bigchaindb.config_utils import file_config - monkeypatch.setattr('bigchaindb.config_utils.file_config', file_config) - - @pytest.fixture(scope='function', autouse=True) def clean_config(monkeypatch): monkeypatch.setattr('bigchaindb.config', copy.deepcopy(ORIGINAL_CONFIG)) diff --git a/tests/test_core.py b/tests/test_core.py index 16974098..5dfbc028 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -91,9 +91,10 @@ def test_has_previous_vote(monkeypatch): bigchain.has_previous_vote(block) -@pytest.mark.parametrize('count,exists', ((1, True), (0, False))) -def test_transaction_exists(monkeypatch, count, exists): +@pytest.mark.parametrize('exists', (True, False)) +def test_transaction_exists(monkeypatch, exists): from bigchaindb.core import Bigchain - monkeypatch.setattr(RqlQuery, 'run', lambda x, y: count) + monkeypatch.setattr( + 'bigchaindb.backend.query.has_transaction', lambda x, y: exists) bigchain = Bigchain(public_key='pubkey', private_key='privkey') assert bigchain.transaction_exists('txid') is exists diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 00000000..ca51fb6e --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,43 @@ +from functools import singledispatch + +import rethinkdb as r + +from bigchaindb.backend.mongodb.connection import MongoDBConnection +from bigchaindb.backend.rethinkdb.connection import RethinkDBConnection + + +@singledispatch +def list_dbs(connection): + raise NotImplementedError + + +@list_dbs.register(RethinkDBConnection) +def list_rethink_dbs(connection): + return connection.run(r.db_list()) + + +@list_dbs.register(MongoDBConnection) +def list_mongo_dbs(connection): + raise NotImplementedError + + +@singledispatch +def flush_db(connection, dbname): + raise NotImplementedError + + +@flush_db.register(RethinkDBConnection) +def flush_rethink_db(connection, dbname): + try: + connection.run(r.db(dbname).table('bigchain').delete()) + connection.run(r.db(dbname).table('backlog').delete()) + connection.run(r.db(dbname).table('votes').delete()) + except r.ReqlOpFailedError: + pass + + +@flush_db.register(MongoDBConnection) +def flush_mongo_db(connection, dbname): + connection.conn[dbname].bigchain.delete_many({}) + connection.conn[dbname].backlog.delete_many({}) + connection.conn[dbname].votes.delete_many({}) diff --git a/tests/web/conftest.py b/tests/web/conftest.py index 61e51ba9..272483e0 100644 --- a/tests/web/conftest.py +++ b/tests/web/conftest.py @@ -2,7 +2,7 @@ import pytest @pytest.fixture -def app(request, restore_config): +def app(request): from bigchaindb.web import server app = server.create_app(debug=True) return app diff --git a/tests/web/test_transactions.py b/tests/web/test_transactions.py index e6f2022e..fc1d02ac 100644 --- a/tests/web/test_transactions.py +++ b/tests/web/test_transactions.py @@ -8,6 +8,7 @@ from bigchaindb.common import crypto TX_ENDPOINT = '/api/v1/transactions/' +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_get_transaction_endpoint(b, client, user_pk): input_tx = b.get_owned_ids(user_pk).pop() @@ -17,6 +18,7 @@ def test_get_transaction_endpoint(b, client, user_pk): assert res.status_code == 200 +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_get_transaction_returns_404_if_not_found(client): res = client.get(TX_ENDPOINT + '123') @@ -26,7 +28,7 @@ def test_get_transaction_returns_404_if_not_found(client): assert res.status_code == 404 -@pytest.mark.usefixtures('setup_database') +@pytest.mark.bdb def test_post_create_transaction_endpoint(b, client): from bigchaindb.models import Transaction user_priv, user_pub = crypto.generate_key_pair() @@ -120,6 +122,7 @@ def test_post_invalid_transaction(client, exc, msg, monkeypatch): 'Invalid transaction ({}): {}'.format(exc, msg)) +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_post_transfer_transaction_endpoint(b, client, user_pk, user_sk): sk, pk = crypto.generate_key_pair() @@ -130,7 +133,8 @@ def test_post_transfer_transaction_endpoint(b, client, user_pk, user_sk): input_valid = b.get_owned_ids(user_pk).pop() create_tx = b.get_transaction(input_valid.txid) transfer_tx = Transaction.transfer(create_tx.to_inputs(), - [([user_pub], 1)], create_tx.asset) + [([user_pub], 1)], + asset_id=create_tx.id) transfer_tx = transfer_tx.sign([user_sk]) res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict())) @@ -139,6 +143,7 @@ def test_post_transfer_transaction_endpoint(b, client, user_pk, user_sk): assert res.json['outputs'][0]['public_keys'][0] == user_pub +@pytest.mark.bdb @pytest.mark.usefixtures('inputs') def test_post_invalid_transfer_transaction_returns_400(b, client, user_pk, user_sk): from bigchaindb.models import Transaction @@ -148,12 +153,14 @@ def test_post_invalid_transfer_transaction_returns_400(b, client, user_pk, user_ input_valid = b.get_owned_ids(user_pk).pop() create_tx = b.get_transaction(input_valid.txid) transfer_tx = Transaction.transfer(create_tx.to_inputs(), - [([user_pub], 1)], create_tx.asset) + [([user_pub], 1)], + asset_id=create_tx.id) 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() @@ -167,6 +174,7 @@ def test_get_transaction_status_endpoint(b, client, user_pk): 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") diff --git a/tests/web/test_unspents.py b/tests/web/test_unspents.py index e5cb3ab1..9539c664 100644 --- a/tests/web/test_unspents.py +++ b/tests/web/test_unspents.py @@ -1,10 +1,10 @@ import pytest +pytestmark = [pytest.mark.bdb, pytest.mark.usefixtures('inputs')] UNSPENTS_ENDPOINT = '/api/v1/unspents/' -@pytest.mark.usefixtures('inputs') def test_get_unspents_endpoint(b, client, user_pk): expected = [u.to_uri('..') for u in b.get_owned_ids(user_pk)] res = client.get(UNSPENTS_ENDPOINT + '?public_key={}'.format(user_pk)) @@ -12,13 +12,11 @@ def test_get_unspents_endpoint(b, client, user_pk): assert res.status_code == 200 -@pytest.mark.usefixtures('inputs') def test_get_unspents_endpoint_without_public_key(client): res = client.get(UNSPENTS_ENDPOINT) assert res.status_code == 400 -@pytest.mark.usefixtures('inputs') def test_get_unspents_endpoint_with_unused_public_key(client): expected = [] res = client.get(UNSPENTS_ENDPOINT + '?public_key=abc')