diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8e64bb1d..13617b77 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,7 +9,7 @@ v3.2.1 - Add support for JSON strings to the `DICT` replacement. Example: [DICT:{"key": true}] - Add `REPLACE` replacement, to replace a substring with another. Example: [REPLACE:[CONTEXT:some_url]::https::http] - Add `TITLE` replacement, to apply Python's title() function. Example: [TITLE:the title] - +- Add `ROUND` replacement, float number to a string with the expected decimals. Example: [ROUND:3.3333::2] v3.2.0 ------ diff --git a/toolium/test/utils/test_dataset_map_param_context.py b/toolium/test/utils/test_dataset_map_param_context.py index 449056e3..292c1ffd 100644 --- a/toolium/test/utils/test_dataset_map_param_context.py +++ b/toolium/test/utils/test_dataset_map_param_context.py @@ -456,6 +456,33 @@ class Context(object): dataset.behave_context = context assert map_param("[CONTEXT:list.cmsScrollableActions.-1.id]") == 'ask-for-negative' +def test_a_context_param_list_incorrect_negative_index(): + """ + Verification of a list with a correct negative index (In bounds) as CONTEXT + """ + class Context(object): + pass + context = Context() + + context.list = { + 'cmsScrollableActions': [ + { + 'id': 'ask-for-duplicate', + 'text': 'QA duplica' + }, + { + 'id': 'ask-for-qa', + 'text': 'QA no duplica' + }, + { + 'id': 'ask-for-negative', + 'text': 'QA negative index' + } + ] + } + dataset.behave_context = context + assert map_param("[CONTEXT:list.cmsScrollableActions.-5.id]") + def test_a_context_param_list_oob_index(): """ diff --git a/toolium/test/utils/test_dataset_replace_param.py b/toolium/test/utils/test_dataset_replace_param.py index 0ba7d065..bec1eb64 100644 --- a/toolium/test/utils/test_dataset_replace_param.py +++ b/toolium/test/utils/test_dataset_replace_param.py @@ -375,6 +375,13 @@ def test_replace_param_dict(): assert param == {'a': 'test1', 'b': 'test2', 'c': 'test3'} +def test_replace_param_dict_json_format(): + param = replace_param('[DICT:{"key": "value", "key_2": true}]') + assert param == '{"key": "value", "key_2": true}' + param = replace_param('[DICT:{"key": "value", "key_2": null}]') + assert param == '{"key": "value", "key_2": null}' + + def test_replace_param_upper(): param = replace_param('[UPPER:test]') assert param == 'TEST' @@ -454,3 +461,10 @@ def test_replace_param_title(): assert param == "Holahola" param = replace_param('[TITLE:hOlA]') assert param == "HOlA" + + +def test_replace_param_round(): + param = replace_param('[ROUND:7.5::2]') + assert param == "7.50" + param = replace_param('[ROUND:3.33333333::3]') + assert param == "3.333" diff --git a/toolium/utils/dataset.py b/toolium/utils/dataset.py index 0eafb24b..781eb687 100644 --- a/toolium/utils/dataset.py +++ b/toolium/utils/dataset.py @@ -84,6 +84,7 @@ def replace_param(param, language='es', infer_param_type=True): [STR:xxxx] Cast xxxx to a string [INT:xxxx] Cast xxxx to an int [FLOAT:xxxx] Cast xxxx to a float + [ROUND:xxxx::2] String float with the expected decimals [LIST:xxxx] Cast xxxx to a list [DICT:xxxx] Cast xxxx to a dict [UPPER:xxxx] Converts xxxx to upper case @@ -228,24 +229,22 @@ def _get_random_phone_number(): def _replace_param_transform_string(param): """ Transform param value according to the specified prefix. - Available transformations: DICT, LIST, INT, FLOAT, STR, UPPER, LOWER, REPLACE, TITLE + Available transformations: DICT, LIST, INT, FLOAT, ROUND, STR, UPPER, LOWER, REPLACE, TITLE :param param: parameter value :return: tuple with replaced value and boolean to know if replacement has been done """ - type_mapping_regex = r'\[(DICT|LIST|INT|FLOAT|STR|UPPER|LOWER|REPLACE|TITLE):([\w\W]*)\]' + type_mapping_regex = r'\[(DICT|LIST|INT|FLOAT|STR|UPPER|LOWER|REPLACE|TITLE|ROUND):([\w\W]*)\]' type_mapping_match_group = re.match(type_mapping_regex, param) new_param = param param_transformed = False if type_mapping_match_group: param_transformed = True - if type_mapping_match_group.group(1) in ['DICT', 'LIST', 'INT', 'FLOAT']: - if '::' in type_mapping_match_group.group() and 'FLOAT' in type_mapping_match_group.group(): - params_to_replace = type_mapping_match_group.group( - 2).split('::') - float_formatted = "{:.2f}".format(round(float(params_to_replace[0]), int(params_to_replace[1]))) - new_param = float_formatted + if type_mapping_match_group.group(1) in ['DICT', 'LIST', 'INT', 'FLOAT', 'ROUND']: + if '::' in type_mapping_match_group.group() and 'ROUND' in type_mapping_match_group.group(): + params_to_replace = type_mapping_match_group.group(2).split('::') + new_param = f"{round(float(params_to_replace[0]), int(params_to_replace[1])):.{int(params_to_replace[1])}f}" elif type_mapping_match_group.group(1) == 'DICT': try: new_param = json.loads(type_mapping_match_group.group(2).strip()) @@ -646,7 +645,8 @@ def get_value_from_context(param, context): if isinstance(value, dict) and part in value: value = value[part] # evaluate if in an array, access is requested by index - elif isinstance(value, list) and part.lstrip('-+').isdigit() and int(part) < len(value): + elif isinstance(value, list) and part.lstrip('-+').isdigit() \ + and int(part) < (len(value) + 1 if part.startswith("-") else len(value)): value = value[int(part)] # or by a key=value expression elif isinstance(value, list) and (element := _select_element_in_list(value, part)):