def test_split_tutorial_workflow(tmp_path, keep_cwd: pathlib.Path, simplified_nist_catalog: oscatalog.Catalog, monkeypatch: MonkeyPatch) -> None: """Test split operations and final re-merge in workflow tutorial.""" # prepare trestle project dir with the file cat_name = 'mycat' trestle_root = test_utils.create_trestle_project_with_model( tmp_path, simplified_nist_catalog, cat_name, monkeypatch) catalog_dir = trestle_root / 'catalogs' / cat_name catalog_file: pathlib.Path = catalog_dir / 'catalog.json' orig_model = oscatalog.Catalog.oscal_read(catalog_file) # step0 os.chdir(catalog_dir) args = argparse.Namespace( file='catalog.json', element='catalog.metadata,catalog.groups,catalog.back-matter', verbose=1, trestle_root=trestle_root) assert SplitCmd()._run(args) == 0 # step1 os.chdir('catalog') args = argparse.Namespace(file='metadata.json', element='metadata.roles,metadata.parties', verbose=1, trestle_root=trestle_root) assert SplitCmd()._run(args) == 0 # step2 os.chdir('metadata') args = argparse.Namespace(file='roles.json', element='roles.*', verbose=1, trestle_root=trestle_root) assert SplitCmd()._run(args) == 0 args = argparse.Namespace(file='parties.json', element='parties.*', verbose=1, trestle_root=trestle_root) assert SplitCmd()._run(args) == 0 # step3 os.chdir('..') args = argparse.Namespace(file='./groups.json', element='groups.*.controls.*', verbose=1, trestle_root=trestle_root) assert SplitCmd()._run(args) == 0 # step4 os.chdir(catalog_dir) args = argparse.Namespace(element='catalog.*', verbose=1, trestle_root=trestle_root) assert MergeCmd()._run(args) == 0 new_model = oscatalog.Catalog.oscal_read(catalog_file) assert test_utils.models_are_equivalent(orig_model, new_model)
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_no_file_given(tmp_path, keep_cwd: pathlib.Path, simplified_nist_catalog: oscatalog.Catalog, monkeypatch: MonkeyPatch) -> None: """Test split with no file specified.""" # prepare trestle project dir with the file cat_name = 'mycat' trestle_root = test_utils.create_trestle_project_with_model( tmp_path, simplified_nist_catalog, cat_name, monkeypatch) orig_model: oscatalog.Catalog = simplified_nist_catalog catalog_dir = trestle_root / 'catalogs' / cat_name catalog_file: pathlib.Path = catalog_dir / 'catalog.json' # no file given and cwd not in trestle directory should fail os.chdir(tmp_path) args = argparse.Namespace(file=None, element='catalog.groups', verbose=1, trestle_root=trestle_root) assert SplitCmd()._run(args) == 1 os.chdir(catalog_dir) args = argparse.Namespace(file=None, element='catalog.groups,catalog.metadata', verbose=1, trestle_root=trestle_root) assert SplitCmd()._run(args) == 0 assert (catalog_dir / 'catalog/groups.json').exists() assert (catalog_dir / 'catalog/metadata.json').exists() os.chdir('./catalog') args = argparse.Namespace(file=None, element='groups.*', verbose=1, trestle_root=trestle_root) assert SplitCmd()._run(args) == 0 assert (catalog_dir / 'catalog/groups/00000__group.json').exists() os.chdir('./groups') args = argparse.Namespace(file='00000__group.json', element='group.*', verbose=1, trestle_root=trestle_root) assert SplitCmd()._run(args) == 0 os.chdir(catalog_dir) args = argparse.Namespace(file=None, element='catalog.*', verbose=1, trestle_root=trestle_root) assert MergeCmd()._run(args) == 0 new_model: oscatalog.Catalog = oscatalog.Catalog.oscal_read(catalog_file) assert test_utils.models_are_equivalent(orig_model, new_model)
def test_split_model_at_path_chain_failures(tmp_dir, sample_catalog: oscatalog.Catalog): """Test for split_model_at_path_chain method failure scenarios.""" content_type = FileContentType.JSON # prepare trestle project dir with the file catalog_dir, catalog_file = test_utils.prepare_trestle_project_dir( tmp_dir, content_type, sample_catalog, test_utils.CATALOGS_DIR) split_plan = Plan() element_paths = [ElementPath('catalog.metadata.parties.*')] # long chain of path should error with pytest.raises(TrestleError): SplitCmd.split_model_at_path_chain(sample_catalog, element_paths, catalog_dir, content_type, 0, split_plan, False) # no plan should error with pytest.raises(TrestleError): SplitCmd.split_model_at_path_chain(sample_catalog, element_paths, catalog_dir, content_type, 0, None, False) # negative path index should error with pytest.raises(TrestleError): SplitCmd.split_model_at_path_chain(sample_catalog, element_paths, catalog_dir, content_type, -1, split_plan, False) # too large path index should return the path index cur_path_index = len(element_paths) + 1 cur_path_index == SplitCmd.split_model_at_path_chain( sample_catalog, element_paths, catalog_dir, content_type, cur_path_index, split_plan, False) # invalid model path should return withour doing anything element_paths = [ElementPath('catalog.meta')] cur_path_index = 0 cur_path_index == SplitCmd.split_model_at_path_chain( sample_catalog, element_paths, catalog_dir, content_type, cur_path_index, split_plan, False) # invalid path for multi item sub-model p0 = ElementPath( 'catalog.uuid.*' ) # uuid exists, but it is not a multi-item sub-model object p1 = ElementPath( 'uuid.metadata.*', p0 ) # this is invalid but we just need a path with the p0 as the parent element_paths = [p0, p1] with pytest.raises(TrestleError): SplitCmd.split_model_at_path_chain(sample_catalog, element_paths, catalog_dir, content_type, 0, split_plan, False)
def test_split_comp_def( mode, tmp_path, keep_cwd: pathlib.Path, sample_component_definition: component.ComponentDefinition, monkeypatch: MonkeyPatch) -> None: """Test splitting of component definition and its dictionary.""" compdef_name = 'mycomp' trestle_root = test_utils.create_trestle_project_with_model( tmp_path, sample_component_definition, compdef_name, monkeypatch) compdef_dir = trestle_root / 'component-definitions' / compdef_name compdef_file: pathlib.Path = compdef_dir / 'component-definition.json' original_model = sample_component_definition os.chdir(compdef_dir) # do the split in different ways - then re-merge if mode == 'normal_split.*': args = argparse.Namespace(file='component-definition.json', element='component-definition.components.*', verbose=1, trestle_root=trestle_root) assert SplitCmd()._run(args) == 0 elif mode == 'split_two_steps': args = argparse.Namespace(file='component-definition.json', element='component-definition.components', verbose=1, trestle_root=trestle_root) assert SplitCmd()._run(args) == 0 os.chdir('component-definition') args = argparse.Namespace(file='components.json', element='components.*', verbose=1, trestle_root=trestle_root) assert SplitCmd()._run(args) == 0 elif mode == 'split_in_lower_dir': args = argparse.Namespace( file='component-definition.json', element='component-definition.components.*.props', verbose=1, trestle_root=trestle_root) assert SplitCmd()._run(args) == 0 os.chdir(compdef_dir) args = argparse.Namespace(element='component-definition.*', verbose=1, trestle_root=trestle_root) assert MergeCmd()._run(args) == 0 new_model = component.ComponentDefinition.oscal_read(compdef_file) assert test_utils.models_are_equivalent(new_model, original_model)
def test_split_relative_path(tmp_path, keep_cwd: pathlib.Path, simplified_nist_catalog: oscatalog.Catalog, monkeypatch: MonkeyPatch) -> None: """Test split with relative path.""" # prepare trestle project dir with the file cat_name = 'mycat' trestle_root = test_utils.create_trestle_project_with_model( tmp_path, simplified_nist_catalog, cat_name, monkeypatch) orig_model: oscatalog.Catalog = simplified_nist_catalog os.chdir(trestle_root) catalog_dir = trestle_root / 'catalogs' / cat_name catalog_file: pathlib.Path = catalog_dir / 'catalog.json' args = argparse.Namespace(file='catalogs/mycat/catalog.json', element='catalog.metadata', verbose=1, trestle_root=trestle_root) assert SplitCmd()._run(args) == 0 # merge receives an element path not a file path # so need to chdir to where the file is os.chdir(catalog_dir) args = argparse.Namespace(element='catalog.*', verbose=1, trestle_root=trestle_root) assert MergeCmd()._run(args) == 0 new_model: oscatalog.Catalog = oscatalog.Catalog.oscal_read(catalog_file) assert test_utils.models_are_equivalent(orig_model, new_model)
def test_split_deep(tmp_path, keep_cwd: pathlib.Path, simplified_nist_catalog: oscatalog.Catalog, monkeypatch: MonkeyPatch) -> None: """Test deep split of model.""" # prepare trestle project dir with the file cat_name = 'mycat' trestle_root = test_utils.create_trestle_project_with_model( tmp_path, simplified_nist_catalog, cat_name, monkeypatch) orig_model: oscatalog.Catalog = simplified_nist_catalog catalog_dir = trestle_root / 'catalogs' / cat_name catalog_file: pathlib.Path = catalog_dir / 'catalog.json' os.chdir(catalog_dir) args = argparse.Namespace(file='catalog.json', element='catalog.groups.*.controls.*.controls.*', verbose=1, trestle_root=trestle_root) assert SplitCmd()._run(args) == 0 args = argparse.Namespace(element='catalog.*', verbose=1, trestle_root=trestle_root) assert MergeCmd()._run(args) == 0 new_model: oscatalog.Catalog = oscatalog.Catalog.oscal_read(catalog_file) assert test_utils.models_are_equivalent(orig_model, new_model)
def test_split_merge(testdata_dir: pathlib.Path, tmp_trestle_dir: pathlib.Path) -> None: """Test merging data that has been split using the split command- to ensure symmetry.""" # trestle split -f catalog.json -e catalog.groups.*.controls.* # prepare trestle project dir with the file test_utils.ensure_trestle_config_dir(tmp_trestle_dir) test_data_source = testdata_dir / 'split_merge/step0-merged_catalog/catalogs' catalogs_dir = Path('catalogs/') mycatalog_dir = catalogs_dir / 'mycatalog' # Copy files from test/data/split_merge/step4 shutil.rmtree(catalogs_dir) shutil.copytree(test_data_source, catalogs_dir) os.chdir(mycatalog_dir) catalog_file = Path('catalog.json') # Read and store the catalog before split stripped_catalog_type, _ = ModelUtils.get_stripped_model_type( catalog_file.resolve(), tmp_trestle_dir) pre_split_catalog = stripped_catalog_type.oscal_read(catalog_file) assert 'groups' in pre_split_catalog.__fields__.keys() # Split the catalog args = argparse.Namespace(name='split', file='catalog.json', verbose=1, element='catalog.groups.*.controls.*', trestle_root=tmp_trestle_dir) split = SplitCmd()._run(args) assert split == 0 interim_catalog_type, _ = ModelUtils.get_stripped_model_type( catalog_file.resolve(), tmp_trestle_dir) interim_catalog = interim_catalog_type.oscal_read(catalog_file.resolve()) assert 'groups' not in interim_catalog.__fields__.keys() # Merge everything back into the catalog # Equivalent to trestle merge -e catalog.* args = argparse.Namespace(name='merge', element='catalog.*', verbose=2, trestle_root=tmp_trestle_dir) rc = MergeCmd()._run(args) assert rc == 0 # Check both the catalogs are the same. post_catalog_type, _ = ModelUtils.get_stripped_model_type( catalog_file.resolve(), tmp_trestle_dir) post_merge_catalog = post_catalog_type.oscal_read(catalog_file) assert post_merge_catalog == pre_split_catalog
def test_split_model_at_path_chain_failures( tmp_path, simplified_nist_catalog: oscatalog.Catalog) -> None: """Test for split_model_at_path_chain method failure scenarios.""" 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) split_plan = Plan() element_paths = [ElementPath('catalog.metadata.parties.*')] # no plan should error with pytest.raises(TrestleError): SplitCmd.split_model_at_path_chain(simplified_nist_catalog, element_paths, catalog_dir, content_type, 0, None, False, '', None) # negative path index should error with pytest.raises(TrestleError): SplitCmd.split_model_at_path_chain(simplified_nist_catalog, element_paths, catalog_dir, content_type, -1, split_plan, False, '', None) # too large path index should return the path index cur_path_index = len(element_paths) + 1 SplitCmd.split_model_at_path_chain(simplified_nist_catalog, element_paths, catalog_dir, content_type, cur_path_index, split_plan, False, '', None)
def test_split_stop_at_string(tmp_path, keep_cwd: pathlib.Path, simplified_nist_catalog: oscatalog.Catalog, monkeypatch: MonkeyPatch) -> None: """Test prevention of split at string level.""" # prepare trestle project dir with the file cat_name = 'mycat' trestle_root = test_utils.create_trestle_project_with_model( tmp_path, simplified_nist_catalog, cat_name, monkeypatch) catalog_dir = trestle_root / 'catalogs' / cat_name os.chdir(catalog_dir) args = argparse.Namespace( file='catalog.json', element='catalog.groups.*.controls.*.controls.*.id', verbose=1, trestle_root=trestle_root) assert SplitCmd()._run(args) == 1 args = argparse.Namespace(file='catalog.json', element='catalog.metadata.version', verbose=1, trestle_root=trestle_root) assert SplitCmd()._run(args) == 1
def test_split_run(tmp_path: pathlib.Path, sample_target_def: ostarget.TargetDefinition) -> None: """Test split run.""" # common variables target_def_dir: pathlib.Path = tmp_path / 'target-definitions' / 'mytarget' target_def_file: pathlib.Path = target_def_dir / 'target-definition.yaml' cwd = os.getcwd() args = {} cmd = SplitCmd() # inner function for checking split files def check_split_files(): assert target_def_dir.joinpath( 'target-definition/metadata.yaml').exists() assert target_def_dir.joinpath('target-definition.yaml').exists() assert target_def_dir.joinpath('target-definition/targets').exists() assert target_def_dir.joinpath('target-definition/targets').is_dir() targets: Dict = Element(sample_target_def).get_at( ElementPath('target-definition.targets.*')) for uuid in targets: target_file = target_def_dir / f'target-definition/targets/{uuid}{const.IDX_SEP}defined-target.yaml' assert target_file.exists() assert trash.to_trash_file_path(target_def_file).exists() # prepare trestle project dir with the file def prepare_target_def_file() -> None: test_utils.ensure_trestle_config_dir(tmp_path) target_def_dir.mkdir(exist_ok=True, parents=True) sample_target_def.oscal_write(target_def_file) # test prepare_target_def_file() args = argparse.Namespace( file='target-definition.yaml', element='target-definition.targets.*,target-definition.metadata', verbose=0) os.chdir(target_def_dir) cmd._run(args) os.chdir(cwd) check_split_files() # clean before the next test test_utils.clean_tmp_path(target_def_dir) # reverse order test prepare_target_def_file() args = argparse.Namespace( file='target-definition.yaml', element='target-definition.metadata,target-definition.targets.*', verbose=0) os.chdir(target_def_dir) cmd._run(args) os.chdir(cwd) check_split_files()
def test_split_run(tmp_dir, sample_target_def: ostarget.TargetDefinition): """Test split run.""" # common variables target_def_dir: pathlib.Path = tmp_dir / 'target-definitions' / 'mytarget' target_def_file: pathlib.Path = target_def_dir / 'target-definition.yaml' cwd = os.getcwd() args = {} cmd = SplitCmd() parser = cmd.parser # inner function for checking split files def check_split_files(): assert target_def_dir.joinpath( 'target-definition/metadata.yaml').exists() assert target_def_dir.joinpath('target-definition.yaml').exists() assert target_def_dir.joinpath('target-definition/targets').exists() assert target_def_dir.joinpath('target-definition/targets').is_dir() targets: dict = Element(sample_target_def).get_at( ElementPath('target-definition.targets.*')) for uuid in targets: target_file = target_def_dir / f'target-definition/targets/{uuid}{const.IDX_SEP}target.yaml' assert target_file.exists() assert trash.to_trash_file_path(target_def_file).exists() # prepare trestle project dir with the file def prepare_target_def_file(): test_utils.ensure_trestle_config_dir(tmp_dir) fs.ensure_directory(target_def_dir) sample_target_def.oscal_write(target_def_file) # test prepare_target_def_file() args = parser.parse_args([ '-f', 'target-definition.yaml', '-e', 'target-definition.targets.*,target-definition.metadata' ]) os.chdir(target_def_dir) cmd._run(args) os.chdir(cwd) check_split_files() # clean before the next test test_utils.clean_tmp_dir(target_def_dir) # reverse order test prepare_target_def_file() args = parser.parse_args([ '-f', 'target-definition.yaml', '-e', 'target-definition.metadata,target-definition.targets.*' ]) os.chdir(target_def_dir) cmd._run(args) os.chdir(cwd) check_split_files()
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_validate_distributed(testdata_dir: pathlib.Path, tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPatch) -> None: """Check that validate will run correctly when exploiting load distributed.""" test_utils.ensure_trestle_config_dir(tmp_trestle_dir) # Clean up. test_data_source = testdata_dir / 'split_merge/step0-merged_catalog/catalogs' catalogs_dir = tmp_trestle_dir / 'catalogs' shutil.rmtree(catalogs_dir) shutil.copytree(test_data_source, catalogs_dir) args = argparse.Namespace(name='split', file='catalogs/mycatalog/catalog.json', verbose=1, element='catalog.groups.*.controls.*', trestle_root=tmp_trestle_dir) _ = SplitCmd()._run(args) test_args = 'trestle validate -a'.split(' ') monkeypatch.setattr(sys, 'argv', test_args) with pytest.raises(SystemExit) as pytest_wrapped_e: cli.run() assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 0
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_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
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_merge_out_of_context(testdata_dir, tmp_trestle_dir, rel_context_dir: str, use_absolutes: bool, split_elem: str, merge_elem: str, use_effective_cwd: bool): """Test merging data that has been split using the split command- to ensure symmetry.""" # trestle split -f catalog.json -e catalog.groups.*.controls.* # prepare trestle project dir with the file - could e cleaned up. test_utils.ensure_trestle_config_dir(tmp_trestle_dir) test_data_source = testdata_dir / 'split_merge/step0-merged_catalog/catalogs/' # Pontentially change to NIST DIR catalogs_dir = Path('catalogs/') shutil.rmtree(catalogs_dir) shutil.copytree(test_data_source, catalogs_dir) full_path_to_model_dir = tmp_trestle_dir / 'catalogs' / 'mycatalog' full_path_to_model = full_path_to_model_dir / 'catalog.json' full_context_dir = tmp_trestle_dir / rel_context_dir if use_absolutes: model_file = full_path_to_model else: model_file = full_path_to_model.relative_to(full_context_dir) # Always use full context dir for safety os.chdir(full_context_dir) # Read and store the catalog before split stripped_catalog_type, _ = ModelUtils.get_stripped_model_type( full_path_to_model.resolve(), tmp_trestle_dir) pre_split_catalog = stripped_catalog_type.oscal_read(full_path_to_model) assert 'groups' in pre_split_catalog.__fields__.keys() # Split the catalog args = argparse.Namespace(name='split', file=model_file, verbose=0, element=split_elem, trestle_root=tmp_trestle_dir) split = SplitCmd()._run(args) assert split == 0 interim_catalog_type, _ = ModelUtils.get_stripped_model_type( full_path_to_model.resolve(), tmp_trestle_dir) interim_catalog = interim_catalog_type.oscal_read( full_path_to_model.resolve()) assert 'groups' not in interim_catalog.__fields__.keys() # Merge everything back into the catalog # Equivalent to trestle merge -e catalog.* if use_effective_cwd: plan = MergeCmd.merge(full_path_to_model_dir, ElementPath(merge_elem), trestle_root=tmp_trestle_dir) else: os.chdir(full_path_to_model_dir) plan = MergeCmd.merge(pathlib.Path.cwd(), ElementPath(merge_elem), trestle_root=tmp_trestle_dir) plan.execute() # Check both the catalogs are the same. post_catalog_type, _ = ModelUtils.get_stripped_model_type( full_path_to_model.resolve(), tmp_trestle_dir) post_merge_catalog = post_catalog_type.oscal_read(full_path_to_model) assert post_merge_catalog == pre_split_catalog
def test_split_run( keep_cwd: pathlib.Path, tmp_path: pathlib.Path, sample_nist_component_def: component.ComponentDefinition) -> None: """Test split run.""" # common variables owd = keep_cwd component_def_dir: pathlib.Path = tmp_path / 'component-definitions' / 'mytarget' component_def_file: pathlib.Path = component_def_dir / 'component-definition.yaml' args = {} cmd = SplitCmd() # inner function for checking split files def check_split_files(): assert component_def_dir.joinpath( 'component-definition/metadata.yaml').exists() assert component_def_dir.joinpath('component-definition.yaml').exists() assert component_def_dir.joinpath( 'component-definition/components').exists() assert component_def_dir.joinpath( 'component-definition/components').is_dir() # Confirm that the list items are written with the expected numbered names components: list = Element(sample_nist_component_def).get_at( ElementPath('component-definition.components.*')) for index in range(len(components)): comp_fname = f'{str(index).zfill(const.FILE_DIGIT_PREFIX_LENGTH)}{const.IDX_SEP}defined-component.yaml' component_file = component_def_dir / 'component-definition' / 'components' / comp_fname assert component_file.exists() assert trash.to_trash_file_path(component_def_file).exists() # prepare trestle project dir with the file def prepare_component_def_file() -> None: test_utils.ensure_trestle_config_dir(tmp_path) component_def_dir.mkdir(exist_ok=True, parents=True) sample_nist_component_def.oscal_write(component_def_file) # test prepare_component_def_file() args = argparse.Namespace( file='component-definition.yaml', element= 'component-definition.components.*,component-definition.metadata', verbose=0, trestle_root=tmp_path) os.chdir(component_def_dir) assert cmd._run(args) == 0 os.chdir(owd) check_split_files() # clean before the next test test_utils.clean_tmp_path(component_def_dir) # reverse order test prepare_component_def_file() args = argparse.Namespace( file='component-definition.yaml', element= 'component-definition.metadata,component-definition.components.*', verbose=0, trestle_root=tmp_path) os.chdir(component_def_dir) assert cmd._run(args) == 0 os.chdir(owd) check_split_files()