from os.path import dirname from robot.api import TestSuiteBuilder from analysis.parserobotfile import ParseRobotFile from analysis.parseapiutilsfile import ParseApiUtilsFile from analysis.parsevariablesfile import ParseVariablesFile import re class GenerateRobotData: def __init__(self, robot_file: str, execdir: str): self.robot_file = robot_file self.execdir = execdir self.config_variables = ParseVariablesFile() self.robot = ParseRobotFile(filename=robot_file, execdir=execdir, config_file=self.config_variables) self.apiutil = ParseApiUtilsFile(filename=self.robot.resource_file) self.robot.set_apiutils(self.apiutil) self.suite = TestSuiteBuilder().build(robot_file) self.test_cases = list() self.test_suite = dict() self.tags_template = list() self.documentation_template = str() self.arguments = list() self.args = list() self.identifier = { 'ContextInformation': 'CI', 'CommonBehaviours': 'CB', 'Consumption': 'CONS', 'Discovery/RetrieveAvailableAttributeInformation': 'DISC', 'Discovery/RetrieveAvailableEntityTypeInformation': 'DISC', 'Entity/RetrieveEntity': 'E', 'Entities/CreateEntity': 'E', 'Provision': 'Prov' } self.references = { 'v1.3.1': 'ETSI GS CIM 009 V1.3.1 [], clause ' } self.initial_conditions = { 'Setup Initial Entity': 'with {\n the SUT containing an initial Entity ${entity} with an id set to ${entityId}\n}', 'Initial State': 'with {\n the SUT in the "initial state"\n}' } self.headers = { '${CONTENT_TYPE_LD_JSON}': 'Content-Type' } self.ids = { '${ENTITIES_ENDPOINT_PATH}': '${entityId}' } self.base_TP_id = str() def get_info(self): self.test_suite['robotpath'] = (self.robot_file.replace(f'{self.execdir}/TP/NGSI-LD/', '') .replace(f'/{self.robot.test_suite}.robot', '')) self.test_suite['robotfile'] = self.robot.test_suite return self.test_suite def parse_robot(self): self.start_suite() _ = [self.visit_test(test=x) for x in self.suite.tests] _ = [self.get_step_data(test=x.name) for x in self.suite.tests] self.test_suite['test_cases'] = self.test_cases # Generate the permutation keys to provide the keys in test_cases list that are different self.test_suite['permutations'] = self.get_permutation_keys(data=self.test_suite['test_cases']) def get_permutation_keys(self, data): all_keys = set().union(*data) excluded_keys = ['doc', 'permutation_tp_id', 'setup', 'teardown', 'name', 'tags'] all_keys = [x for x in all_keys if x not in excluded_keys] keys_with_different_values = [ key for key in all_keys if any(d.get(key) != data[0].get(key) for d in data[1:]) ] return keys_with_different_values def get_params(self, test_case: str): # New content # test_case = self.robot.test_cases[string] lines_starting_response = re.findall(r'^\s*\$\{response\}.*', test_case, re.MULTILINE) # If there is more than one line, it means that the test case has several operations, all of them to # create the environment content to execute the last one, which is the correct one to test the Test Case if len(lines_starting_response) > 1: # The last one corresponds to the execution of the test, the rest corresponds to the initial condition of # test case... response_to_check = lines_starting_response[-1] else: response_to_check = lines_starting_response[0] index = test_case.find(response_to_check) aux = test_case[index:].split('\n') # Previuos content params = list() request = str() #index_start = string.find('${response}') #aux = string[index_start:].split('\n') # End previous content # Get the list of params of the function, they are the keys if ' ... ' in aux[1]: request = aux[0].split(' ')[2] # We are in the case that the attributes are in following lines for i in range(1, len(aux)): if ' ... ' in aux[i]: regex = '\s{4}\.{3}\s{4}(.*)' param = re.match(pattern=regex, string=aux[i]) if aux: params.append(param.groups()[0]) else: break else: # the attributes are in the same line regex = r"\s*\$\{response\}=\s{4}(.*)" matches = re.finditer(regex, response_to_check, re.MULTILINE) request = aux[0].split(' ')[2] # We have two options from here, or the parameters are defined in the same line or the parameters are defined in # following lines, next lines for match in matches: # Check that we have 1 group matched if len(match.groups()) == 1: aux = match.group(1) # Get the list of keys params = aux.split(' ')[1:] print(params) else: raise Exception(f"Error, unexpected format, received: '{response_to_check}'") return request, params def get_step_data(self, test: str): # string = self.robot.get_substring(initial_string=test, final_string=self.suite.name, include=False) string = self.robot.test_cases[test] # request, params = self.get_params(string=string) request, params = self.get_params(test_case=string) #self.check_header_parameters(params=params, test=test) verb, url = self.apiutil.get_response(keyword=request) index = None for i, item in enumerate(self.test_cases): if 'name' in item and item['name'] == test: index = i break self.test_cases[index]['http_verb'] = verb self.test_cases[index]['endpoint'] = self.get_values_url(keys=url) self.test_cases[index]['when'] = self.robot.generate_when_content(http_verb=self.test_cases[index]['http_verb'], endpoint=self.test_cases[index]['endpoint'], when=self.test_cases[index]['when']) #expected_status_code = self.robot.get_expected_status_code(keyword='Check Response Status Code') #self.test_cases[index]['expected_status_code'] = expected_status_code def check_header_parameters(self, params: list, test: str): value = str() # 1st case: value of the parameters are sent as it is header_key = [x for x in params if x in self.headers.keys()] if len(header_key) != 0: key = header_key[0] value = self.get_header_value(key=key) else: # 2nd case, maybe the params are defined in the way = for k in self.headers: aux = [x for x in params if k in x] if len(aux) != 0: key = aux[0].split('=')[1] value = self.get_header_value(key=key) else: key = None value = None if key is not None and value is not None: # Iterate over the list and find the index index = None for i, item in enumerate(self.test_cases): if 'name' in item and item['name'] == test: index = i break # self.test_cases[index]['params'] = params self.test_cases[index][self.headers[key]] = value def get_values_url(self, keys: list) -> str: data = [self.get_value_url(key=x) for x in keys] data = '/'.join(data).replace('//', '/') return data def get_value_url(self, key: str) -> str: key_to_search = f'${key}' try: value = self.apiutil.variables[key_to_search] except KeyError: # It is not defined in ApiUtils, maybe in Robot File? try: value = self.robot.variables[key_to_search] except KeyError: # The variable is not defined, so it is keep as it is in the url value = key return value def get_header_value(self, key: str): value = str() # We can have a simple variable or a compound of two variables count = key.count('$') if count == 1: # Get the value of the Header key try: value = self.apiutil.variables[key] except KeyError: # It is not defined in ApiUtils, maybe in Robot File try: value = self.robot.variables[key] except KeyError: # ERROR, the header key is not defined raise Exception(f"ERROR, the header key '{key}' is undefined") elif count == 2: keys = key.split("$") key = f'${keys[1]}' second_key = f'${keys[2]}' try: second_key = self.ids[key] except KeyError: raise Exception(f"ERROR: Need to manage the '{second_key}' in GenerateRobotData::self.ids") # Get the value of the Header key try: value = self.apiutil.variables[key] value = f'{value}{second_key}' except KeyError: # It is not defined in ApiUtils, maybe in Robot File try: value = self.robot.variables[key] value = f'{value}{second_key}' except KeyError: # ERROR, the header key is not defined raise Exception(f"ERROR, the header key '{key}' is undefined") return value def start_suite(self): """Modify suite's tests to contain only every Xth.""" version = 'v1.3.1' tp_id = self.generate_name() reference, pics = self.generate_reference(version=version) self.test_suite = { 'tp_id': tp_id, 'test_objective': self.suite.doc, 'reference': reference, 'config_id': '', 'parent_release': version, 'pics_selection': pics, 'keywords': [str(x) for x in self.suite.keywords], 'teardown': str(self.suite.teardown), 'initial_condition': self.suite.setup, 'test_cases': list() } def visit_test(self, test): # Get Tags associated to the test if len(test.tags) == 0 and self.tags_template is not None: tags = self.tags_template else: tags = list(test.tags) # Get the Documentation associated to the test if len(test.doc) == 0 and self.documentation_template is not None: documentation = self.documentation_template else: documentation = test.doc # Get the Content-Type and Body associated to the Test if len(self.args) != 0: # We are talking about Test Cases with Test Template, so we need to check the keyword content with the # definition of the template # params = self.args[test.name] # index = [index for index, value in enumerate(self.arguments) if value == '${content_type}'][0] # content_type = params[index] # # body = self.get_body(string=test.name) # Generate Checks for Test Data then = self.robot.get_checks(test_name=test.template, apiutils=self.apiutil) else: # We are talking about a Test Cases without Test Template # Generate Checks for Test Data then = self.robot.get_checks(test_name=test.name, apiutils=self.apiutil) # Generate Request for Test Data when = self.robot.get_request(test_name=test.name) test_case = { 'name': test.name, 'permutation_tp_id': f'{self.base_TP_id}/{test.name.split(" ")[0]}', 'doc': documentation, 'tags': tags, 'setup': test.setup.name, 'teardown': test.teardown.name, 'template': test.template, 'then': then, 'when': when } try: self.test_suite['initial_condition'] = self.initial_conditions[test.setup.name] except KeyError: self.test_suite['initial_condition'] = self.initial_conditions['Initial State'] self.test_cases.append(test_case) def get_body(self, string: str) -> str: aux = string.split(' ')[1:] if len(aux) >= 2: aux = [self.get_body(x) for x in aux] aux = ' '.join(aux) elif len(aux) == 1: aux = re.sub(r'([a-z])([A-Z])', r'\1 \2', aux[0]) else: aux = re.sub(r'([a-z])([A-Z])', r'\1 \2', string) return aux def generate_name(self): base_dir = dirname(dirname(dirname(__file__))) tp_id = str(self.suite.source.parent).replace(f'{base_dir}/', "") for key, value in self.identifier.items(): tp_id = tp_id.replace(key, value) self.base_TP_id = tp_id name = self.suite.name.replace(" ", "_") tp_id = f'{self.base_TP_id}/{name}' return tp_id def generate_reference(self, version): # Get the list of tags in the different tests tags = [x.tags for x in self.suite.tests] tags = [element for sublist in tags for element in sublist if element[0].isdigit()] if len(tags) == 0: # We have different tests cases that call a test template reference, pics = self.generate_reference_template(version=version) else: # We have normal tests cases reference, pics = self.generate_reference_testcases(tags=tags, version=version) return reference, pics def generate_reference_template(self, version): # Get the list of arguments, we select the first one because the 2nd keyword corresponds # to the teardown operation args = [list(x.keywords)[0] for x in self.suite.tests] args = [{str(x.parent): list(x.args)} for x in args] self.args = dict() _ = [self.args.update(x) for x in args] template_name = list(set([list(x.keywords)[0].name for x in self.suite.tests]))[0] # Due to the information of the tags are contained in the Keyword description of the template, we need to # analyse the Keyword. string = self.robot.get_substring(initial_string='** Keywords ***', final_string='', include=False) reference, pics = self.get_info_from_template(name=template_name, string=string, version=version) return reference, pics def get_info_from_template(self, name: str, string: str, version: str): # TODO: Check that the name of the template is in the string receive print(name) # Get the Tags line and the tag value tags = self.get_substring(string=string, key='[Tags]') self.tags_template = tags[1:] tag = list(set([element for sublist in tags for element in tags if element[0].isdigit()]))[0] reference = f'{self.references[version]}{tag.replace("_", ".")}' pics = f'PICS_{tag}' # Get the arguments self.arguments = self.get_substring(string=string, key='[Arguments]') self.arguments = self.arguments[1:] # Get the documentation self.documentation_template = self.get_substring(string=string, key='[Documentation]') self.documentation_template = self.documentation_template[1:][0] return reference, pics def get_substring(self, string: str, key: str): pos1 = string.find(key) - 1 pos2 = string[pos1:].find('\n') result = string[pos1:pos1+pos2].split(' ') return result def generate_reference_testcases(self, tags: list, version: str): check_tags = all(item == tags[0] for item in tags) if check_tags is False or len(tags) == 0: print(f'ERROR: the Test Suite {self.suite.name} has different clauses or no clauses (Tags): {tags}\n' f'Unable to select the corresponding Reference of this Test Suite') reference = '' pics = '' else: # All the clauses are the same, so we select the first one reference = f'{self.references[version]}{tags[0].replace("_", ".")}' pics = f'PICS_{tags[0]}' return reference, pics