def test_element_get_at(sample_nist_component_def: component.ComponentDefinition): """Test element get method.""" element = Element(sample_nist_component_def) # field alias should succeed assert element.get_at( ElementPath('component-definition.metadata.last-modified') ) == sample_nist_component_def.metadata.last_modified # field name should fail assert element.get_at(ElementPath('component-definition.metadata.last_modified')) is None assert element.get() == sample_nist_component_def assert element.get_at() == element.get() assert element.get_at(ElementPath('component-definition.metadata')) == sample_nist_component_def.metadata assert element.get_at( ElementPath('component-definition.metadata.title') ) == sample_nist_component_def.metadata.title assert element.get_at(ElementPath('component-definition.components')) == sample_nist_component_def.components assert element.get_at(ElementPath('component-definition.components.*')) == sample_nist_component_def.components assert element.get_at( ElementPath('component-definition.metadata.parties.*') ) == sample_nist_component_def.metadata.parties assert element.get_at(ElementPath('component-definition.metadata.parties.0') ) == sample_nist_component_def.metadata.parties[0] assert element.get_at(ElementPath('component-definition.metadata.parties.0.uuid') ) == sample_nist_component_def.metadata.parties[0].uuid for index in range(len(sample_nist_component_def.components)): path_str = f'component-definition.components.{index}' assert element.get_at(ElementPath(path_str)) == sample_nist_component_def.components[index] # invalid indexing assert element.get_at(ElementPath('component-definition.metadata.title.0')) is None # invalid path with missing root model assert element.get_at(ElementPath('metadata.title')) is None # element_path with parent path parent_path = ElementPath('component-definition.metadata') element_path = ElementPath('metadata.parties.*', parent_path) assert element.get_at(element_path) == sample_nist_component_def.metadata.parties # element_path with parent path parent_path = ElementPath('component-definition.components.*') element_path = ElementPath('component.control-implementations.*', parent_path) component_list = element.get_at(parent_path) for component_item in component_list: component_element = Element(component_item) assert component_element.get_at(element_path) == component_item.control_implementations # element_path in a list with parent path parent_path = ElementPath('component-definition.components.*') element_path = ElementPath('component.control-implementations.0', parent_path) component_list = element.get_at(parent_path) for component_item in component_list: component_element = Element(component_item) assert component_element.get_at(element_path) == component_item.control_implementations[0]
def test_element_get_at(sample_target_def: target.TargetDefinition): """Test element get method.""" element = Element(sample_target_def) # field alias should succeed assert element.get_at( ElementPath('target-definition.metadata.last-modified') ) == sample_target_def.metadata.last_modified # field name should fail assert element.get_at(ElementPath('target-definition.metadata.last_modified')) is None assert element.get() == sample_target_def assert element.get_at() == element.get() assert element.get_at(ElementPath('target-definition.metadata')) == sample_target_def.metadata assert element.get_at(ElementPath('target-definition.metadata.title')) == sample_target_def.metadata.title assert element.get_at(ElementPath('target-definition.targets')) == sample_target_def.targets assert element.get_at(ElementPath('target-definition.targets.*')) == sample_target_def.targets assert element.get_at(ElementPath('target-definition.metadata.parties.*')) == sample_target_def.metadata.parties assert element.get_at(ElementPath('target-definition.metadata.parties.0')) == sample_target_def.metadata.parties[0] assert element.get_at(ElementPath('target-definition.metadata.parties.0.uuid') ) == sample_target_def.metadata.parties[0].uuid for uuid in sample_target_def.targets: path_str = f'target-definition.targets.{uuid}' assert element.get_at(ElementPath(path_str)) == sample_target_def.targets[uuid] # invalid indexing assert element.get_at(ElementPath('target-definition.metadata.title.0')) is None # invalid path with missing root model assert element.get_at(ElementPath('metadata.title')) is None # element_path with parent path parent_path = ElementPath('target-definition.metadata') element_path = ElementPath('metadta.parties.*', parent_path) assert element.get_at(element_path) == sample_target_def.metadata.parties # element_path with parent path parent_path = ElementPath('target-definition.targets.*') element_path = ElementPath('target.target-control-implementations.*', parent_path) targets = element.get_at(parent_path) for key in targets: target = targets[key] target_element = Element(target) assert target_element.get_at(element_path) == target.target_control_implementations # element_path in a list with parent path parent_path = ElementPath('target-definition.targets.*') element_path = ElementPath('target.target-control-implementations.0', parent_path) targets = element.get_at(parent_path) for key in targets: target = targets[key] target_element = Element(target) assert target_element.get_at(element_path) == target.target_control_implementations[0]
def test_write_file_action_yaml(tmp_yaml_file: pathlib.Path, sample_nist_component_def): """Test write yaml action.""" element = Element(sample_nist_component_def, 'component-definition') try: # string path should error wa = WriteFileAction(tmp_yaml_file.as_posix(), element, FileContentType.YAML) except TrestleError: pass try: # invalid file extension should error wa = WriteFileAction(tmp_yaml_file, element, FileContentType.JSON) except TrestleError: pass try: # writing to a non-existing file will error wa = WriteFileAction(tmp_yaml_file, element, FileContentType.YAML) wa.execute() except TrestleError: pass tmp_yaml_file.touch() wa = WriteFileAction(tmp_yaml_file, element, FileContentType.YAML) wa.execute() test_utils.verify_file_content(tmp_yaml_file, element.get())
def test_split_chained_sub_model_plans( tmp_path: pathlib.Path, simplified_nist_catalog: oscatalog.Catalog, keep_cwd: pathlib.Path) -> None: """Test for split_model method with chained sum models like catalog.metadata.parties.*.""" # Assume we are running a command like below # trestle split -f catalog.json -e catalog.metadata.parties.* # see https://github.com/IBM/compliance-trestle/issues/172 content_type = FileContentType.JSON # prepare trestle project dir with the file catalog_dir, catalog_file = test_utils.prepare_trestle_project_dir( tmp_path, content_type, simplified_nist_catalog, test_utils.CATALOGS_DIR) # read the model from file catalog = oscatalog.Catalog.oscal_read(catalog_file) element = Element(catalog) element_args = ['catalog.metadata.parties.*'] element_paths = cmd_utils.parse_element_args( None, element_args, catalog_dir.relative_to(tmp_path)) assert 2 == len(element_paths) expected_plan = Plan() # prepare to extract metadata and parties metadata_file = catalog_dir / element_paths[0].to_file_path(content_type) metadata_field_alias = element_paths[0].get_element_name() metadata = element.get_at(element_paths[0]) meta_element = Element(metadata, metadata_field_alias) # extract parties parties_dir = catalog_dir / 'catalog/metadata/parties' for i, party in enumerate(meta_element.get_at(element_paths[1], False)): prefix = str(i).zfill(const.FILE_DIGIT_PREFIX_LENGTH) sub_model_actions = SplitCmd.prepare_sub_model_split_actions( party, parties_dir, prefix, content_type) expected_plan.add_actions(sub_model_actions) # stripped metadata stripped_metadata = metadata.stripped_instance( stripped_fields_aliases=['parties']) expected_plan.add_action(CreatePathAction(metadata_file)) expected_plan.add_action( WriteFileAction(metadata_file, Element(stripped_metadata, metadata_field_alias), content_type)) # stripped catalog root_file = catalog_dir / element_paths[0].to_root_path(content_type) remaining_root = element.get().stripped_instance(metadata_field_alias) expected_plan.add_action(CreatePathAction(root_file, True)) expected_plan.add_action( WriteFileAction(root_file, Element(remaining_root), content_type)) split_plan = SplitCmd.split_model(catalog, element_paths, catalog_dir, content_type, '', None) assert expected_plan == split_plan
def test_write_action_json(tmp_json_file, sample_target_def): """Test write json action.""" element = Element(sample_target_def, 'target-definition') with open(tmp_json_file, 'w+') as writer: wa = WriteAction(writer, element, FileContentType.JSON) wa.execute() test_utils.verify_file_content(tmp_json_file, element.get()) os.remove(tmp_json_file)
def test_write_action_yaml(tmp_yaml_file, sample_target): """Test write yaml action.""" element = Element(sample_target) with open(tmp_yaml_file, 'w+') as writer: wa = WriteAction(writer, element, FileContentType.YAML) wa.execute() test_utils.verify_file_content(tmp_yaml_file, element.get()) os.remove(tmp_yaml_file)
def test_element_get_at(sample_target: target.TargetDefinition): """Test element get method.""" element = Element(sample_target) assert element.get() == sample_target assert element.get_at() == element.get() assert element.get_at(ElementPath('metadata')) == sample_target.metadata assert element.get_at( ElementPath('metadata.title')) == sample_target.metadata.title assert element.get_at(ElementPath('targets')) == sample_target.targets assert element.get_at(ElementPath('targets.*')) == sample_target.targets assert element.get_at( ElementPath('metadata.parties.*')) == sample_target.metadata.parties assert element.get_at( ElementPath('metadata.parties.0')) == sample_target.metadata.parties[0] assert element.get_at(ElementPath( 'metadata.parties.0.uuid')) == sample_target.metadata.parties[0].uuid # invalid indexing assert element.get_at(ElementPath('metadata.title.0')) is None
def test_write_action_json(tmp_json_file, sample_nist_component_def): """Test write json action.""" element = Element(sample_nist_component_def, 'component-definition') with open(tmp_json_file, 'w+', encoding=const.FILE_ENCODING) as writer: wa = WriteAction(writer, element, FileContentType.JSON) wa.execute() writer.flush() writer.close() test_utils.verify_file_content(tmp_json_file, element.get()) os.remove(tmp_json_file)
def test_write_file_rollback(tmp_yaml_file: pathlib.Path, sample_target): """Test rollback.""" element = Element(sample_target) tmp_yaml_file.touch() wa = WriteFileAction(tmp_yaml_file, element, FileContentType.YAML) wa.execute() test_utils.verify_file_content(tmp_yaml_file, element.get()) # verify the file content is not empty with open(tmp_yaml_file, 'r', encoding='utf8') as read_file: assert read_file.tell() >= 0 wa.rollback() # verify the file content is empty with open(tmp_yaml_file, 'r', encoding='utf8') as read_file: assert read_file.tell() == 0
def test_write_file_rollback(tmp_yaml_file: pathlib.Path, sample_nist_component_def): """Test rollback.""" element = Element(sample_nist_component_def, 'component-definition') tmp_yaml_file.touch() wa = WriteFileAction(tmp_yaml_file, element, FileContentType.YAML) wa.execute() test_utils.verify_file_content(tmp_yaml_file, element.get()) # verify the file content is not empty with open(tmp_yaml_file, 'r', encoding=const.FILE_ENCODING) as read_file: assert read_file.tell() >= 0 wa.rollback() # verify the file content is empty with open(tmp_yaml_file, 'r', encoding=const.FILE_ENCODING) as read_file: assert read_file.tell() == 0
def add(element_path: ElementPath, parent_element: Element, include_optional: bool) -> None: """For a element_path, add a child model to the parent_element of a given parent_model. Args: element_path: element path of the item to create within the model parent_element: the parent element that will host the created element include_optional: whether to create optional attributes in the created element Notes: First we find the child model at the specified element path and instantiate it with default values. Then we check if there's already existing element at that path, in which case we append the child model to the existing list of dict. Then we set up an action plan to update the model (specified by file_path) in memory, create a file at the same location and write the file. We update the parent_element to prepare for next adds in the chain """ if '*' in element_path.get_full_path_parts(): raise err.TrestleError('trestle add does not support Wildcard element path.') # Get child model try: child_model = element_path.get_type(type(parent_element.get())) # Create child element with sample values child_object = gens.generate_sample_model(child_model, include_optional=include_optional) if parent_element.get_at(element_path) is not None: # The element already exists if type(parent_element.get_at(element_path)) is list: child_object = parent_element.get_at(element_path) + child_object elif type(parent_element.get_at(element_path)) is dict: child_object = {**parent_element.get_at(element_path), **child_object} else: raise err.TrestleError('Already exists and is not a list or dictionary.') except Exception as e: raise err.TrestleError(f'Bad element path. {str(e)}') update_action = UpdateAction( sub_element=child_object, dest_element=parent_element, sub_element_path=element_path ) parent_element = parent_element.set_at(element_path, child_object) return update_action, parent_element
def test_split_model_plans( tmp_path: pathlib.Path, sample_nist_component_def: component.ComponentDefinition) -> None: """Test for split_model method.""" # Assume we are running a command like below # trestle split -f component-definition.yaml -e component-definition.metadata content_type = FileContentType.YAML # prepare trestle project dir with the file component_def_dir, component_def_file = test_utils.prepare_trestle_project_dir( tmp_path, content_type, sample_nist_component_def, test_utils.COMPONENT_DEF_DIR) # read the model from file component_def = component.ComponentDefinition.oscal_read( component_def_file) element = Element(component_def) element_args = ['component-definition.metadata'] element_paths = cmd_utils.parse_element_args(None, element_args) # extract values metadata_file = component_def_dir / element_paths[0].to_file_path( content_type) metadata = element.get_at(element_paths[0]) root_file = component_def_dir / element_paths[0].to_root_path(content_type) remaining_root = element.get().stripped_instance( element_paths[0].get_element_name()) # prepare the plan expected_plan = Plan() expected_plan.add_action(CreatePathAction(metadata_file)) expected_plan.add_action( WriteFileAction(metadata_file, Element(metadata), content_type)) expected_plan.add_action(CreatePathAction(root_file, True)) expected_plan.add_action( WriteFileAction(root_file, Element(remaining_root), content_type)) split_plan = SplitCmd.split_model(component_def, element_paths, component_def_dir, content_type, '', None) assert expected_plan == split_plan
def test_split_model(tmp_dir, sample_target_def: ostarget.TargetDefinition): """Test for split_model method.""" # Assume we are running a command like below # trestle split -f target-definition.yaml -e target-definition.metadata content_type = FileContentType.YAML # prepare trestle project dir with the file target_def_dir, target_def_file = test_utils.prepare_trestle_project_dir( tmp_dir, content_type, sample_target_def, test_utils.TARGET_DEFS_DIR) # read the model from file target_def = ostarget.TargetDefinition.oscal_read(target_def_file) element = Element(target_def) element_args = ['target-definition.metadata'] element_paths = cmd_utils.parse_element_args(element_args) # extract values metadata_file = target_def_dir / element_paths[0].to_file_path( content_type) metadata = element.get_at(element_paths[0]) root_file = target_def_dir / element_paths[0].to_root_path(content_type) remaining_root = element.get().stripped_instance( element_paths[0].get_element_name()) # prepare the plan expected_plan = Plan() expected_plan.add_action(CreatePathAction(metadata_file)) expected_plan.add_action( WriteFileAction(metadata_file, Element(metadata), content_type)) expected_plan.add_action(CreatePathAction(root_file, True)) expected_plan.add_action( WriteFileAction(root_file, Element(remaining_root), content_type)) split_plan = SplitCmd.split_model(target_def, element_paths, target_def_dir, content_type) assert expected_plan == split_plan
def test_split_multi_level_dict( tmp_path: pathlib.Path, sample_target_def: ostarget.TargetDefinition) -> None: """Test for split_model method.""" # Assume we are running a command like below # trestle split -f target.yaml -e target-definition.targets.*.target-control-implementations.* content_type = FileContentType.YAML # prepare trestle project dir with the file target_def_dir, target_def_file = test_utils.prepare_trestle_project_dir( tmp_path, content_type, sample_target_def, test_utils.TARGET_DEFS_DIR) file_ext = FileContentType.to_file_extension(content_type) # read the model from file target_def: ostarget.TargetDefinition = ostarget.TargetDefinition.oscal_read( target_def_file) element = Element(target_def) element_args = [ 'target-definition.targets.*.target-control-implementations.*' ] element_paths = test_utils.prepare_element_paths(target_def_dir, element_args) expected_plan = Plan() # extract values targets: dict = element.get_at(element_paths[0]) targets_dir = target_def_dir / element_paths[0].to_file_path() # split every targets for key in targets: # individual target dir target: ostarget.Target = targets[key] target_element = Element(targets[key]) model_type = utils.classname_to_alias(type(target).__name__, 'json') dir_prefix = key target_dir_name = f'{dir_prefix}{const.IDX_SEP}{model_type}' target_file = targets_dir / f'{target_dir_name}{file_ext}' # target control impl dir for the target target_ctrl_impls: dict = target_element.get_at(element_paths[1]) targets_ctrl_dir = targets_dir / element_paths[1].to_file_path( root_dir=target_dir_name) for i, target_ctrl_impl in enumerate(target_ctrl_impls): model_type = utils.classname_to_alias( type(target_ctrl_impl).__name__, 'json') file_prefix = str(i).zfill(const.FILE_DIGIT_PREFIX_LENGTH) file_name = f'{file_prefix}{const.IDX_SEP}{model_type}{file_ext}' file_path = targets_ctrl_dir / file_name expected_plan.add_action(CreatePathAction(file_path)) expected_plan.add_action( WriteFileAction(file_path, Element(target_ctrl_impl), content_type)) # write stripped target model stripped_target = target.stripped_instance( stripped_fields_aliases=[element_paths[1].get_element_name()]) expected_plan.add_action(CreatePathAction(target_file)) expected_plan.add_action( WriteFileAction(target_file, Element(stripped_target), content_type)) root_file = target_def_dir / f'target-definition{file_ext}' remaining_root = element.get().stripped_instance( stripped_fields_aliases=[element_paths[0].get_element_name()]) expected_plan.add_action(CreatePathAction(root_file, True)) expected_plan.add_action( WriteFileAction(root_file, Element(remaining_root), content_type)) split_plan = SplitCmd.split_model(target_def, element_paths, target_def_dir, content_type) assert expected_plan == split_plan
def test_subsequent_split_model( tmp_path: pathlib.Path, sample_target_def: ostarget.TargetDefinition) -> None: """Test subsequent split of sub models.""" # Assume we are running a command like below # trestle split -f target-definition.yaml -e target-definition.metadata content_type = FileContentType.YAML # prepare trestle project dir with the file target_def_dir, target_def_file = test_utils.prepare_trestle_project_dir( tmp_path, content_type, sample_target_def, test_utils.TARGET_DEFS_DIR) # first split the target-def into metadata target_def = ostarget.TargetDefinition.oscal_read(target_def_file) element = Element(target_def, 'target-definition') element_args = ['target-definition.metadata'] element_paths = test_utils.prepare_element_paths(target_def_dir, element_args) metadata_file = target_def_dir / element_paths[0].to_file_path( content_type) metadata: ostarget.Metadata = element.get_at(element_paths[0]) root_file = target_def_dir / element_paths[0].to_root_path(content_type) metadata_field_alias = element_paths[0].get_element_name() stripped_root = element.get().stripped_instance( stripped_fields_aliases=[metadata_field_alias]) root_wrapper_alias = utils.classname_to_alias( stripped_root.__class__.__name__, 'json') first_plan = Plan() first_plan.add_action(CreatePathAction(metadata_file)) first_plan.add_action( WriteFileAction(metadata_file, Element(metadata, metadata_field_alias), content_type)) first_plan.add_action(CreatePathAction(root_file, True)) first_plan.add_action( WriteFileAction(root_file, Element(stripped_root, root_wrapper_alias), content_type)) first_plan.execute() # this will split the files in the temp directory # now, prepare the expected plan to split metadta at parties second_plan = Plan() metadata_file_dir = target_def_dir / element_paths[0].to_root_path() metadata2 = ostarget.Metadata.oscal_read(metadata_file) element = Element(metadata2, metadata_field_alias) element_args = ['metadata.parties.*'] element_paths = test_utils.prepare_element_paths(target_def_dir, element_args) parties_dir = metadata_file_dir / element_paths[0].to_file_path() for i, party in enumerate(element.get_at(element_paths[0])): prefix = str(i).zfill(const.FILE_DIGIT_PREFIX_LENGTH) sub_model_actions = SplitCmd.prepare_sub_model_split_actions( party, parties_dir, prefix, content_type) second_plan.add_actions(sub_model_actions) # stripped metadata stripped_metadata = metadata2.stripped_instance( stripped_fields_aliases=['parties']) second_plan.add_action(CreatePathAction(metadata_file, True)) second_plan.add_action( WriteFileAction(metadata_file, Element(stripped_metadata, metadata_field_alias), content_type)) # call the split command and compare the plans split_plan = SplitCmd.split_model(metadata, element_paths, metadata_file_dir, content_type) assert second_plan == split_plan
def test_split_multi_level_dict_plans( tmp_path: pathlib.Path, sample_nist_component_def: component.ComponentDefinition, keep_cwd) -> None: """Test for split_model method.""" # Assume we are running a command like below # trestle split -f target.yaml -e component-definition.components.*.control-implementations.* content_type = FileContentType.YAML # prepare trestle project dir with the file component_def_dir, component_def_file = test_utils.prepare_trestle_project_dir( tmp_path, content_type, sample_nist_component_def, test_utils.COMPONENT_DEF_DIR) file_ext = FileContentType.to_file_extension(content_type) # read the model from file component_def: component.ComponentDefinition = component.ComponentDefinition.oscal_read( component_def_file) element = Element(component_def) element_args = [ 'component-definition.components.*.control-implementations.*' ] element_paths = cmd_utils.parse_element_args( None, element_args, component_def_dir.relative_to(tmp_path)) expected_plan = Plan() # extract values components: list = element.get_at(element_paths[0]) components_dir = component_def_dir / element_paths[0].to_file_path() # split every targets for index, comp_obj in enumerate(components): # individual target dir component_element = Element(comp_obj) model_type = str_utils.classname_to_alias( type(comp_obj).__name__, AliasMode.JSON) dir_prefix = str(index).zfill(const.FILE_DIGIT_PREFIX_LENGTH) component_dir_name = f'{dir_prefix}{const.IDX_SEP}{model_type}' component_file = components_dir / f'{component_dir_name}{file_ext}' # target control impl dir for the target component_ctrl_impls: list = component_element.get_at(element_paths[1]) component_ctrl_dir = components_dir / element_paths[1].to_file_path( root_dir=component_dir_name) for i, component_ctrl_impl in enumerate(component_ctrl_impls): model_type = str_utils.classname_to_alias( type(component_ctrl_impl).__name__, AliasMode.JSON) file_prefix = str(i).zfill(const.FILE_DIGIT_PREFIX_LENGTH) file_name = f'{file_prefix}{const.IDX_SEP}{model_type}{file_ext}' file_path = component_ctrl_dir / file_name expected_plan.add_action(CreatePathAction(file_path)) expected_plan.add_action( WriteFileAction(file_path, Element(component_ctrl_impl), content_type)) # write stripped target model stripped_target = comp_obj.stripped_instance( stripped_fields_aliases=[element_paths[1].get_element_name()]) expected_plan.add_action(CreatePathAction(component_file)) expected_plan.add_action( WriteFileAction(component_file, Element(stripped_target), content_type)) root_file = component_def_dir / f'component-definition{file_ext}' remaining_root = element.get().stripped_instance( stripped_fields_aliases=[element_paths[0].get_element_name()]) expected_plan.add_action(CreatePathAction(root_file, True)) expected_plan.add_action( WriteFileAction(root_file, Element(remaining_root), content_type)) split_plan = SplitCmd.split_model(component_def, element_paths, component_def_dir, content_type, '', None) assert expected_plan == split_plan