Merge branch 'bug/1670/asset-language-api-fix'

This commit is contained in:
kansi 2017-11-09 13:41:20 +05:30
commit b95dc306d8
4 changed files with 127 additions and 16 deletions

View File

@ -16,10 +16,17 @@ import logging
import bigchaindb
from bigchaindb.backend.connection import connect
from bigchaindb.common.exceptions import ValidationError
from bigchaindb.common.utils import validate_all_values_for_key
logger = logging.getLogger(__name__)
TABLES = ('bigchain', 'backlog', 'votes', 'assets')
VALID_LANGUAGES = ('danish', 'dutch', 'english', 'finnish', 'french', 'german',
'hungarian', 'italian', 'norwegian', 'portuguese', 'romanian',
'russian', 'spanish', 'swedish', 'turkish', 'none',
'da', 'nl', 'en', 'fi', 'fr', 'de', 'hu', 'it', 'nb', 'pt',
'ro', 'ru', 'es', 'sv', 'tr')
@singledispatch
@ -99,3 +106,44 @@ def init_database(connection=None, dbname=None):
create_database(connection, dbname)
create_tables(connection, dbname)
create_indexes(connection, dbname)
def validate_language_key(obj, key):
"""Validate all nested "language" key in `obj`.
Args:
obj (dict): dictionary whose "language" key is to be validated.
Returns:
None: validation successful
Raises:
ValidationError: will raise exception in case language is not valid.
"""
backend = bigchaindb.config['database']['backend']
if backend == 'mongodb':
data = obj.get(key, {})
if isinstance(data, dict):
validate_all_values_for_key(data, 'language', validate_language)
def validate_language(value):
"""Check if `value` is a valid language.
https://docs.mongodb.com/manual/reference/text-search-languages/
Args:
value (str): language to validated
Returns:
None: validation successful
Raises:
ValidationError: will raise exception in case language is not valid.
"""
if value not in VALID_LANGUAGES:
error_str = ('MongoDB does not support text search for the '
'language "{}". If you do not understand this error '
'message then please rename key/field "language" to '
'something else like "lang".').format(value)
raise ValidationError(error_str)

View File

@ -52,53 +52,73 @@ def deserialize(data):
def validate_txn_obj(obj_name, obj, key, validation_fun):
"""Validates value associated to `key` in `obj` by applying
`validation_fun`.
"""Validate value of `key` in `obj` using `validation_fun`.
Args:
obj_name (str): name for `obj` being validated.
obj (dict): dictonary object.
obj (dict): dictionary object.
key (str): key to be validated in `obj`.
validation_fun (function): function used to validate the value
of `key`.
Returns:
None: indicates validation successfull
None: indicates validation successful
Raises:
ValidationError: `validation_fun` will raise this error on failure
ValidationError: `validation_fun` will raise exception on failure
"""
backend = bigchaindb.config['database']['backend']
if backend == 'mongodb':
data = obj.get(key, {}) or {}
validate_all_keys(obj_name, data, validation_fun)
data = obj.get(key, {})
if isinstance(data, dict):
validate_all_keys(obj_name, data, validation_fun)
def validate_all_keys(obj_name, obj, validation_fun):
"""Validates all (nested) keys in `obj` by using `validation_fun`
"""Validate all (nested) keys in `obj` by using `validation_fun`.
Args:
obj_name (str): name for `obj` being validated.
obj (dict): dictonary object.
obj (dict): dictionary object.
validation_fun (function): function used to validate the value
of `key`.
Returns:
None: indicates validation successfull
None: indicates validation successful
Raises:
ValidationError: `validation_fun` will raise this error on failure
"""
for key, value in obj.items():
validation_fun(obj_name, key)
if type(value) is dict:
if isinstance(value, dict):
validate_all_keys(obj_name, value, validation_fun)
return
def validate_all_values_for_key(obj, key, validation_fun):
"""Validate value for all (nested) occurrence of `key` in `obj`
using `validation_fun`.
Args:
obj (dict): dictionary object.
key (str): key whose value is to be validated.
validation_fun (function): function used to validate the value
of `key`.
Raises:
ValidationError: `validation_fun` will raise this error on failure
"""
for vkey, value in obj.items():
if vkey == key:
validation_fun(value)
elif isinstance(value, dict):
validate_all_values_for_key(value, key, validation_fun)
def validate_key(obj_name, key):
"""Check if `key` contains ".", "$" or null characters
"""Check if `key` contains ".", "$" or null characters.
https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names
Args:
@ -106,13 +126,13 @@ def validate_key(obj_name, key):
key (str): key to validated
Returns:
None: indicates validation successfull
None: validation successful
Raises:
ValidationError: raise execption incase of regex match.
ValidationError: will raise exception in case of regex match.
"""
if re.search(r'^[$]|\.|\x00', key):
error_str = ('Invalid key name "{}" in {} object. The '
'key name cannot contain characters '
'".", "$" or null characters').format(key, obj_name)
raise ValidationError(error_str) from ValueError()
raise ValidationError(error_str)

View File

@ -11,6 +11,7 @@ from bigchaindb.common.transaction import Transaction
from bigchaindb.common.utils import (gen_timestamp, serialize,
validate_txn_obj, validate_key)
from bigchaindb.common.schema import validate_transaction_schema
from bigchaindb.backend.schema import validate_language_key
class Transaction(Transaction):
@ -87,6 +88,7 @@ class Transaction(Transaction):
validate_transaction_schema(tx_body)
validate_txn_obj('asset', tx_body['asset'], 'data', validate_key)
validate_txn_obj('metadata', tx_body, 'metadata', validate_key)
validate_language_key(tx_body['asset'], 'data')
return super().from_dict(tx_body)
@classmethod

View File

@ -47,6 +47,47 @@ def test_post_create_transaction_endpoint(b, client):
assert res.json['outputs'][0]['public_keys'][0] == user_pub
@pytest.mark.parametrize("nested", [False, True])
@pytest.mark.parametrize("language,expected_status_code", [
('danish', 202), ('dutch', 202), ('english', 202), ('finnish', 202),
('french', 202), ('german', 202), ('hungarian', 202), ('italian', 202),
('norwegian', 202), ('portuguese', 202), ('romanian', 202), ('none', 202),
('russian', 202), ('spanish', 202), ('swedish', 202), ('turkish', 202),
('da', 202), ('nl', 202), ('en', 202), ('fi', 202), ('fr', 202),
('de', 202), ('hu', 202), ('it', 202), ('nb', 202), ('pt', 202),
('ro', 202), ('ru', 202), ('es', 202), ('sv', 202), ('tr', 202),
('any', 400)
])
@pytest.mark.language
@pytest.mark.bdb
def test_post_create_transaction_with_language(b, client, nested, language,
expected_status_code):
from bigchaindb.models import Transaction
from bigchaindb.backend.mongodb.connection import MongoDBConnection
if isinstance(b.connection, MongoDBConnection):
user_priv, user_pub = crypto.generate_key_pair()
lang_obj = {'language': language}
if nested:
asset = {'root': lang_obj}
else:
asset = lang_obj
tx = Transaction.create([user_pub], [([user_pub], 1)],
asset=asset)
tx = tx.sign([user_priv])
res = client.post(TX_ENDPOINT, data=json.dumps(tx.to_dict()))
assert res.status_code == expected_status_code
if res.status_code == 400:
expected_error_message = (
'Invalid transaction (ValidationError): MongoDB does not support '
'text search for the language "{}". If you do not understand this '
'error message then please rename key/field "language" to something '
'else like "lang".').format(language)
assert res.json['message'] == expected_error_message
@pytest.mark.parametrize("field", ['asset', 'metadata'])
@pytest.mark.parametrize("value,err_key,expected_status_code", [
({'bad.key': 'v'}, 'bad.key', 400),