mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Merge branch 'bug/1670/asset-language-api-fix'
This commit is contained in:
commit
b95dc306d8
@ -16,10 +16,17 @@ import logging
|
|||||||
|
|
||||||
import bigchaindb
|
import bigchaindb
|
||||||
from bigchaindb.backend.connection import connect
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
TABLES = ('bigchain', 'backlog', 'votes', 'assets')
|
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
|
@singledispatch
|
||||||
@ -99,3 +106,44 @@ def init_database(connection=None, dbname=None):
|
|||||||
create_database(connection, dbname)
|
create_database(connection, dbname)
|
||||||
create_tables(connection, dbname)
|
create_tables(connection, dbname)
|
||||||
create_indexes(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)
|
||||||
|
@ -52,53 +52,73 @@ def deserialize(data):
|
|||||||
|
|
||||||
|
|
||||||
def validate_txn_obj(obj_name, obj, key, validation_fun):
|
def validate_txn_obj(obj_name, obj, key, validation_fun):
|
||||||
"""Validates value associated to `key` in `obj` by applying
|
"""Validate value of `key` in `obj` using `validation_fun`.
|
||||||
`validation_fun`.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
obj_name (str): name for `obj` being validated.
|
obj_name (str): name for `obj` being validated.
|
||||||
obj (dict): dictonary object.
|
obj (dict): dictionary object.
|
||||||
key (str): key to be validated in `obj`.
|
key (str): key to be validated in `obj`.
|
||||||
validation_fun (function): function used to validate the value
|
validation_fun (function): function used to validate the value
|
||||||
of `key`.
|
of `key`.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
None: indicates validation successfull
|
None: indicates validation successful
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValidationError: `validation_fun` will raise this error on failure
|
ValidationError: `validation_fun` will raise exception on failure
|
||||||
"""
|
"""
|
||||||
backend = bigchaindb.config['database']['backend']
|
backend = bigchaindb.config['database']['backend']
|
||||||
|
|
||||||
if backend == 'mongodb':
|
if backend == 'mongodb':
|
||||||
data = obj.get(key, {}) or {}
|
data = obj.get(key, {})
|
||||||
|
if isinstance(data, dict):
|
||||||
validate_all_keys(obj_name, data, validation_fun)
|
validate_all_keys(obj_name, data, validation_fun)
|
||||||
|
|
||||||
|
|
||||||
def validate_all_keys(obj_name, obj, 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:
|
Args:
|
||||||
obj_name (str): name for `obj` being validated.
|
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
|
validation_fun (function): function used to validate the value
|
||||||
of `key`.
|
of `key`.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
None: indicates validation successfull
|
None: indicates validation successful
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValidationError: `validation_fun` will raise this error on failure
|
ValidationError: `validation_fun` will raise this error on failure
|
||||||
"""
|
"""
|
||||||
for key, value in obj.items():
|
for key, value in obj.items():
|
||||||
validation_fun(obj_name, key)
|
validation_fun(obj_name, key)
|
||||||
if type(value) is dict:
|
if isinstance(value, dict):
|
||||||
validate_all_keys(obj_name, value, validation_fun)
|
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):
|
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
|
https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -106,13 +126,13 @@ def validate_key(obj_name, key):
|
|||||||
key (str): key to validated
|
key (str): key to validated
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
None: indicates validation successfull
|
None: validation successful
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValidationError: raise execption incase of regex match.
|
ValidationError: will raise exception in case of regex match.
|
||||||
"""
|
"""
|
||||||
if re.search(r'^[$]|\.|\x00', key):
|
if re.search(r'^[$]|\.|\x00', key):
|
||||||
error_str = ('Invalid key name "{}" in {} object. The '
|
error_str = ('Invalid key name "{}" in {} object. The '
|
||||||
'key name cannot contain characters '
|
'key name cannot contain characters '
|
||||||
'".", "$" or null characters').format(key, obj_name)
|
'".", "$" or null characters').format(key, obj_name)
|
||||||
raise ValidationError(error_str) from ValueError()
|
raise ValidationError(error_str)
|
||||||
|
@ -11,6 +11,7 @@ from bigchaindb.common.transaction import Transaction
|
|||||||
from bigchaindb.common.utils import (gen_timestamp, serialize,
|
from bigchaindb.common.utils import (gen_timestamp, serialize,
|
||||||
validate_txn_obj, validate_key)
|
validate_txn_obj, validate_key)
|
||||||
from bigchaindb.common.schema import validate_transaction_schema
|
from bigchaindb.common.schema import validate_transaction_schema
|
||||||
|
from bigchaindb.backend.schema import validate_language_key
|
||||||
|
|
||||||
|
|
||||||
class Transaction(Transaction):
|
class Transaction(Transaction):
|
||||||
@ -87,6 +88,7 @@ class Transaction(Transaction):
|
|||||||
validate_transaction_schema(tx_body)
|
validate_transaction_schema(tx_body)
|
||||||
validate_txn_obj('asset', tx_body['asset'], 'data', validate_key)
|
validate_txn_obj('asset', tx_body['asset'], 'data', validate_key)
|
||||||
validate_txn_obj('metadata', tx_body, 'metadata', validate_key)
|
validate_txn_obj('metadata', tx_body, 'metadata', validate_key)
|
||||||
|
validate_language_key(tx_body['asset'], 'data')
|
||||||
return super().from_dict(tx_body)
|
return super().from_dict(tx_body)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -47,6 +47,47 @@ def test_post_create_transaction_endpoint(b, client):
|
|||||||
assert res.json['outputs'][0]['public_keys'][0] == user_pub
|
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("field", ['asset', 'metadata'])
|
||||||
@pytest.mark.parametrize("value,err_key,expected_status_code", [
|
@pytest.mark.parametrize("value,err_key,expected_status_code", [
|
||||||
({'bad.key': 'v'}, 'bad.key', 400),
|
({'bad.key': 'v'}, 'bad.key', 400),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user