def test_run(tmp_path: pathlib.Path, sample_catalog_missing_roles, monkeypatch: MonkeyPatch): """Test _run for RemoveCmd.""" # 1. Test trestle remove for one element. # expected catalog after remove of Responsible-Party file_path = pathlib.Path.joinpath( test_utils.JSON_TEST_DATA_PATH, 'minimal_catalog_no_responsible-parties.json') expected_catalog_no_rp = Catalog.oscal_read(file_path) content_type = FileContentType.JSON # Create a temporary file with responsible-parties to be removed. catalog_def_dir, catalog_def_file = test_utils.prepare_trestle_project_dir( tmp_path, content_type, sample_catalog_missing_roles, test_utils.CATALOGS_DIR) testargs = [ 'trestle', 'remove', '-tr', str(tmp_path), '-f', str(catalog_def_file), '-e', 'catalog.metadata.responsible-parties' ] monkeypatch.setattr(sys, 'argv', testargs) assert Trestle().run() == 0 actual_catalog = Catalog.oscal_read(catalog_def_file) assert expected_catalog_no_rp == actual_catalog # 2. Test trestle remove for multiple comma-separated elements. # minimal catalog with Roles and Resposibile-Parties. file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH, 'minimal_catalog_roles.json') catalog_with_roles_responsible_parties = Catalog.oscal_read(file_path) # Create a temporary file with Roles and Responsible-Parties to be removed. catalog_def_dir, catalog_def_file_2 = test_utils.prepare_trestle_project_dir( tmp_path, content_type, catalog_with_roles_responsible_parties, test_utils.CATALOGS_DIR) testargs = [ 'trestle', 'remove', '-tr', str(tmp_path), '-f', str(catalog_def_file_2), '-e', 'catalog.metadata.responsible-parties,catalog.metadata.roles' ] monkeypatch.setattr(sys, 'argv', testargs) assert Trestle().run() == 0 actual_catalog = Catalog.oscal_read(catalog_def_file_2) assert expected_catalog_no_rp == actual_catalog
def test_href_cmd(tmp_path: pathlib.Path, keep_cwd: pathlib.Path, simplified_nist_profile: profile.Profile, monkeypatch: MonkeyPatch) -> None: """Test basic cmd invocation of href.""" # prepare trestle project dir with the file models_path, profile_path = test_utils.prepare_trestle_project_dir( tmp_path, FileContentType.JSON, simplified_nist_profile, test_utils.PROFILES_DIR) os.chdir(models_path) # just list the hrefs cmd_string = 'trestle href -n my_test_model' monkeypatch.setattr(sys, 'argv', cmd_string.split()) rc = Trestle().run() assert rc == 0 orig_href = simplified_nist_profile.imports[0].href new_href = 'trestle://catalogs/my_catalog/catalog.json' cmd_string = f'trestle href -n my_test_model -hr {new_href}' monkeypatch.setattr(sys, 'argv', cmd_string.split()) rc = Trestle().run() assert rc == 0 # confirm new href is correct new_profile: profile.Profile = profile.Profile.oscal_read(profile_path) assert new_profile.imports[0].href == new_href # restore orig href to confirm models are otherwise equivalent # only thing different should be last-modified new_profile.imports[0].href = orig_href assert test_utils.models_are_equivalent(new_profile, simplified_nist_profile)
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_run_failure_plan_execute(tmp_path: pathlib.Path, sample_catalog_minimal: Catalog, monkeypatch: MonkeyPatch, caplog: pytest.LogCaptureFixture): """Test failure plan execute() in _run on RemoveCmd.""" # Plant this specific logged error for failing execution in mock_execute: logged_error = 'fail_execute' def mock_execute(*args, **kwargs): raise err.TrestleError(logged_error) # Create a temporary file as a valid arg for trestle remove: content_type = FileContentType.JSON catalog_def_dir, catalog_def_file = test_utils.prepare_trestle_project_dir( tmp_path, content_type, sample_catalog_minimal, test_utils.CATALOGS_DIR) monkeypatch.chdir(tmp_path) # Add remarks here, so it is a valid removal target, testargs = [ 'trestle', 'create', '-f', str(catalog_def_file), '-e', 'catalog.metadata.remarks' ] monkeypatch.setattr(sys, 'argv', testargs) Trestle().run() # .. then attempt to remove it here, but mocking a failed execute: testargs = [ 'trestle', 'remove', '-f', str(catalog_def_file), '-e', 'catalog.metadata.remarks' ] monkeypatch.setattr(Plan, 'execute', mock_execute) monkeypatch.setattr(sys, 'argv', testargs) caplog.clear() rc = Trestle().run() assert rc > 0
def test_href_failures(tmp_path: pathlib.Path, keep_cwd: pathlib.Path, simplified_nist_profile: profile.Profile, monkeypatch: MonkeyPatch) -> None: """Test href failure modes.""" # prepare trestle project dir with the file models_path, profile_path = test_utils.prepare_trestle_project_dir( tmp_path, FileContentType.JSON, simplified_nist_profile, test_utils.PROFILES_DIR) cmd_string = 'trestle href -n my_test_model -hr foobar' # not in trestle project so fail monkeypatch.setattr(sys, 'argv', cmd_string.split()) rc = Trestle().run() assert rc == 5 os.chdir(models_path) cmd_string = 'trestle href -n my_test_model -hr foobar -i 2' # add extra import to the profile and ask for import number 2 simplified_nist_profile.imports.append(simplified_nist_profile.imports[0]) simplified_nist_profile.oscal_write(profile_path) monkeypatch.setattr(sys, 'argv', cmd_string.split()) rc = Trestle().run() assert rc == 1
def test_run_failure_nonexistent_element(tmp_path: pathlib.Path, sample_catalog_minimal: Catalog, monkeypatch: MonkeyPatch): """Test failure of _run on RemoveCmd in specifying nonexistent element for removal.""" # Create a temporary catalog file with responsible-parties content_type = FileContentType.JSON catalog_def_dir, catalog_def_file = test_utils.prepare_trestle_project_dir( tmp_path, content_type, sample_catalog_minimal, test_utils.CATALOGS_DIR) # 1. self.remove() fails -- Should happen if wildcard is given, or nonexistent element. testargs = [ 'trestle', 'remove', '-f', str(catalog_def_file), '-e', 'catalog.blah' ] monkeypatch.setattr(sys, 'argv', testargs) exitcode = Trestle().run() assert exitcode == 5 # 2. Corrupt json file source_file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH, 'bad_simple.json') shutil.copyfile(source_file_path, catalog_def_file) testargs = [ 'trestle', 'remove', '-f', str(catalog_def_file), '-e', 'catalog.metadata.roles' ] monkeypatch.setattr(sys, 'argv', testargs) exitcode = Trestle().run() assert exitcode == 5
def test_add(tmp_path: pathlib.Path, keep_cwd: pathlib.Path) -> None: """Test Add.add() method used by trestle CreateCmd.""" file_path = pathlib.Path( test_utils.JSON_TEST_DATA_PATH) / 'minimal_catalog_missing_roles.json' minimal_catalog_missing_roles = Catalog.oscal_read(file_path) # expected catalog after first add of Role file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH, 'minimal_catalog_roles.json') expected_catalog_roles1 = Element(Catalog.oscal_read(file_path)) # expected catalog after second add of Role file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH, 'minimal_catalog_roles_double.json') expected_catalog_roles2 = Element(Catalog.oscal_read(file_path)) # expected catalog after add of Responsible-Party file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH, 'minimal_catalog_roles_double_rp.json') expected_catalog_roles2_rp = Element(Catalog.oscal_read(file_path)) content_type = FileContentType.JSON _, _ = test_utils.prepare_trestle_project_dir( tmp_path, content_type, minimal_catalog_missing_roles, test_utils.CATALOGS_DIR) # Execute first _add element_path = ElementPath('catalog.metadata.roles') catalog_element = Element(minimal_catalog_missing_roles) expected_update_action_1 = UpdateAction( expected_catalog_roles1.get_at(element_path), catalog_element, element_path) actual_update_action, actual_catalog_roles = Add.add( element_path, catalog_element, False) assert actual_catalog_roles == expected_catalog_roles1 assert actual_update_action == expected_update_action_1 # Execute second _add - this time roles already exists, so this adds a roles object to roles array catalog_element = actual_catalog_roles expected_update_action_2 = UpdateAction( expected_catalog_roles2.get_at(element_path), catalog_element, element_path) actual_update_action2, actual_catalog_roles2 = Add.add( element_path, catalog_element, False) assert actual_catalog_roles2 == expected_catalog_roles2 assert actual_update_action2 == expected_update_action_2 # Execute _add for responsible-parties to the same catalog element_path = ElementPath('catalog.metadata.responsible-parties') catalog_element = actual_catalog_roles2 expected_update_action_3 = UpdateAction( expected_catalog_roles2_rp.get_at(element_path), catalog_element, element_path) actual_update_action3, actual_catalog_roles2_rp = Add.add( element_path, catalog_element, False) assert actual_catalog_roles2_rp == expected_catalog_roles2_rp assert actual_update_action3 == expected_update_action_3
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_split_model_at_path_chain_failures(tmp_path, 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_path, 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 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 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_run_failure_wildcard(tmp_path, sample_catalog_minimal): """Test failure of _run on RemoveCmd in specifying wildcard in element for removal.""" # Create a temporary catalog file with responsible-parties content_type = FileContentType.JSON catalog_def_dir, catalog_def_file = test_utils.prepare_trestle_project_dir( tmp_path, content_type, sample_catalog_minimal, test_utils.CATALOGS_DIR) testargs = [ 'trestle', 'remove', '-f', str(catalog_def_file), '-e', 'catalog.*' ] with patch.object(sys, 'argv', testargs): exitcode = Trestle().run() assert exitcode == 1
def test_run_failure_project_not_found(tmp_path, sample_catalog_minimal): """Test failure of _run on RemoveCmd in specifying file in non-initialized location.""" # Create a temporary catalog file with responsible-parties content_type = FileContentType.JSON catalog_def_dir, catalog_def_file = test_utils.prepare_trestle_project_dir( tmp_path, content_type, sample_catalog_minimal, test_utils.CATALOGS_DIR) # 5. get_contextual_model_type() fails, i.e., "Trestle project not found" testargs = [ 'trestle', 'remove', '-f', '/dev/null', '-e', 'catalog.metadata' ] with patch.object(sys, 'argv', testargs): exitcode = Trestle().run() assert exitcode == 1
def test_run_failure_required_element(tmp_path, sample_catalog_minimal): """Test failure of _run on RemoveCmd in specifying a required element for removal.""" # Create a temporary catalog file with responsible-parties content_type = FileContentType.JSON catalog_def_dir, catalog_def_file = test_utils.prepare_trestle_project_dir( tmp_path, content_type, sample_catalog_minimal, test_utils.CATALOGS_DIR) # 4. simulate() fails -- Should happen if required element is target for deletion testargs = [ 'trestle', 'remove', '-f', str(catalog_def_file), '-e', 'catalog.metadata' ] with patch.object(sys, 'argv', testargs): exitcode = Trestle().run() assert exitcode == 1
def test_add(tmp_dir, sample_catalog_minimal): """Test AddCmd.add() method for trestle add.""" # expected catalog after first add of Role file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH, 'minimal_catalog_roles.json') expected_catalog_roles1 = Catalog.oscal_read(file_path) # expected catalog after second add of Role file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH, 'minimal_catalog_roles_double.json') expected_catalog_roles2 = Catalog.oscal_read(file_path) # expected catalog after add of Responsible-Party file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH, 'minimal_catalog_roles_double_rp.json') expected_catalog_roles2_rp = Catalog.oscal_read(file_path) content_type = FileContentType.JSON catalog_def_dir, catalog_def_file = test_utils.prepare_trestle_project_dir( tmp_dir, content_type, sample_catalog_minimal, test_utils.CATALOGS_DIR) # Execute first _add element_path = ElementPath('catalog.metadata.roles') catalog_element = Element(sample_catalog_minimal) AddCmd.add(catalog_def_dir / catalog_def_file, element_path, Catalog, catalog_element) actual_catalog_roles = Catalog.oscal_read(catalog_def_dir / catalog_def_file) assert actual_catalog_roles == expected_catalog_roles1 # Execute second _add - this time roles already exists, so this adds a roles object to roles array catalog_element = Element(actual_catalog_roles) AddCmd.add(catalog_def_dir / catalog_def_file, element_path, Catalog, catalog_element) actual_catalog_roles2 = Catalog.oscal_read(catalog_def_dir / catalog_def_file) assert actual_catalog_roles2 == expected_catalog_roles2 # Execute _add for responsible-parties to the same catalog element_path = ElementPath('catalog.metadata.responsible-parties') catalog_element = Element(actual_catalog_roles2) AddCmd.add(catalog_def_dir / catalog_def_file, element_path, Catalog, catalog_element) actual_catalog_roles2_rp = Catalog.oscal_read(catalog_def_dir / catalog_def_file) assert actual_catalog_roles2_rp == expected_catalog_roles2_rp
def test_run_failure_filenotfounderror(tmp_path, sample_catalog_minimal): """Test failure of _run on RemoveCmd in specifying a nonexistent file.""" # Create a temporary catalog file with responsible-parties content_type = FileContentType.JSON catalog_def_dir, catalog_def_file = test_utils.prepare_trestle_project_dir( tmp_path, content_type, sample_catalog_minimal, test_utils.CATALOGS_DIR) # 6. oscal_read fails because file is not found # Must specify catalogs/ location, not catalogs/my_test_model/. testargs = [ 'trestle', 'remove', '-f', re.sub('my_test_model/', '', str(catalog_def_file)), '-e', 'catalog.metadata' ] with patch.object(sys, 'argv', testargs): exitcode = Trestle().run() assert exitcode == 1
def test_run_failure_plan_execute(tmp_path, sample_catalog_minimal): """Test failure plan execute() in _run on RemoveCmd.""" # Create a temporary file as a valid arg for trestle remove: content_type = FileContentType.JSON catalog_def_dir, catalog_def_file = test_utils.prepare_trestle_project_dir( tmp_path, content_type, sample_catalog_minimal, test_utils.CATALOGS_DIR) testargs = [ 'trestle', 'remove', '-f', str(catalog_def_file), '-e', 'catalog.metadata.responsible-parties' ] with mock.patch('trestle.core.models.plans.Plan.simulate'): with mock.patch( 'trestle.core.models.plans.Plan.execute') as execute_mock: execute_mock.side_effect = err.TrestleError('stuff') with patch.object(sys, 'argv', testargs): exitcode = Trestle().run() assert exitcode == 1
def test_stripped_model(tmp_path: pathlib.Path, keep_cwd: pathlib.Path, sample_catalog_minimal: Catalog, monkeypatch: MonkeyPatch) -> None: """Test CreateCmd creating element for stripped model.""" content_type = FileContentType.JSON catalog_def_dir, catalog_def_file = test_utils.prepare_trestle_project_dir( tmp_path, content_type, sample_catalog_minimal, test_utils.CATALOGS_DIR) os.chdir(catalog_def_dir) testargs = [ 'trestle', 'split', '-f', 'catalog.json', '-e', 'catalog.metadata' ] monkeypatch.setattr(sys, 'argv', testargs) assert Trestle().run() == 0 # Now that the metadata has been split, add of catalog.metadata.roles will error, # but add of catalog.back-matter will pass testargs = [ 'trestle', 'create', '-f', 'catalog.json', '-e', 'catalog.metadata.roles' ] monkeypatch.setattr(sys, 'argv', testargs) assert Trestle().run() == 1 testargs = [ 'trestle', 'create', '-f', 'catalog.json', '-e', 'catalog.back-matter' ] current_model, _ = ModelUtils.get_stripped_model_type( catalog_def_dir, tmp_path) current_catalog = current_model.oscal_read(pathlib.Path('catalog.json')) current_catalog.back_matter = BackMatter() expected_catalog = current_catalog monkeypatch.setattr(sys, 'argv', testargs) assert Trestle().run() == 0 actual_model, _ = ModelUtils.get_stripped_model_type( catalog_def_dir, tmp_path) actual_catalog = actual_model.oscal_read(pathlib.Path('catalog.json')) assert expected_catalog == actual_catalog
def test_describe_functionality( element_path: str, tmp_path: pathlib.Path, keep_cwd: pathlib.Path, simplified_nist_catalog: oscatalog.Catalog ) -> None: """Test basic functionality of describe.""" # prepare trestle project dir with the file catalog_dir, catalog_file = test_utils.prepare_trestle_project_dir( tmp_path, FileContentType.JSON, simplified_nist_catalog, test_utils.CATALOGS_DIR) os.chdir(catalog_dir) args = argparse.Namespace(file='catalog.json', element=element_path, verbose=1, trestle_root=tmp_path) assert DescribeCmd()._run(args) == 0 results = DescribeCmd.describe(catalog_file.resolve(), '', tmp_path) assert len(results) > 5 assert 'catalog.json' in results[0] assert 'catalog.Catalog' in results[0]
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_add_failure(tmp_dir, sample_catalog_minimal): """Test AddCmd.add() method for trestle add.""" content_type = FileContentType.JSON catalog_def_dir, catalog_def_file = test_utils.prepare_trestle_project_dir( tmp_dir, content_type, sample_catalog_minimal, test_utils.CATALOGS_DIR) element_path = ElementPath('catalog.metadata.*') catalog_element = Element(sample_catalog_minimal) with pytest.raises(err.TrestleError): AddCmd.add(element_path, Catalog, catalog_element) element_path = ElementPath('catalog.metadata.title') with pytest.raises(err.TrestleError): AddCmd.add(element_path, Catalog, catalog_element) element_path = ElementPath('catalog.metadata.bad_path') with pytest.raises(err.TrestleError): AddCmd.add(element_path, Catalog, catalog_element)
def test_striped_model(tmp_path, sample_catalog_minimal): """Test _run for AddCmd for stripped model.""" cwd = os.getcwd() content_type = FileContentType.JSON catalog_def_dir, catalog_def_file = test_utils.prepare_trestle_project_dir( tmp_path, content_type, sample_catalog_minimal, test_utils.CATALOGS_DIR) os.chdir(catalog_def_dir) testargs = [ 'trestle', 'split', '-f', 'catalog.json', '-e', 'catalog.metadata' ] with patch.object(sys, 'argv', testargs): Trestle().run() # Now that the metadata has been split, add of catalog.metadata.roles will error, # but add of catalog.back-matter will pass testargs = [ 'trestle', 'add', '-f', 'catalog.json', '-e', 'catalog.metadata.roles' ] with patch.object(sys, 'argv', testargs): assert Trestle().run() == 1 testargs = [ 'trestle', 'add', '-f', 'catalog.json', '-e', 'catalog.back-matter' ] current_model, _ = get_stripped_contextual_model() current_catalog = current_model.oscal_read(pathlib.Path('catalog.json')) current_catalog.back_matter = BackMatter() expected_catalog = current_catalog with patch.object(sys, 'argv', testargs): Trestle().run() actual_model, _ = get_stripped_contextual_model() actual_catalog = actual_model.oscal_read(pathlib.Path('catalog.json')) assert expected_catalog == actual_catalog os.chdir(cwd)
def test_describe_failures( tmp_path: pathlib.Path, keep_cwd: pathlib.Path, sample_component_definition: ComponentDefinition ) -> None: """Test describe failure modes.""" comp_def_dir, comp_def_file = test_utils.prepare_trestle_project_dir( tmp_path, FileContentType.JSON, sample_component_definition, test_utils.COMPONENT_DEF_DIR) # not in trestle directory args = argparse.Namespace( file='comp_def.json', element='component-definition.metadata', verbose=1, trestle_root=tmp_path ) assert DescribeCmd()._run(args) == 1 os.chdir(comp_def_dir) # in trestle directory but have wildcard in element path args = argparse.Namespace( file=comp_def_file, element='component-definition.*.roles', verbose=1, trestle_root=tmp_path ) assert DescribeCmd()._run(args) == 1 # in trestle directory but have comma in element path args = argparse.Namespace( file=comp_def_file, element='component-definition.metadata,component-definition.back-matter', verbose=1, trestle_root=tmp_path ) assert DescribeCmd()._run(args) == 1 # in trestle directory but element only has one part args = argparse.Namespace(file=comp_def_file, element='component-definition', verbose=1, trestle_root=tmp_path) assert DescribeCmd()._run(args) == 1 # no filename specified args = argparse.Namespace(file=None, element=None, trestle_root=tmp_path, verbose=1) assert DescribeCmd()._run(args) == 2
def test_add_failure(tmp_path: pathlib.Path, sample_catalog_minimal: Catalog, keep_cwd: pathlib.Path) -> None: """Test Add.add() method failures.""" content_type = FileContentType.JSON _, _ = test_utils.prepare_trestle_project_dir(tmp_path, content_type, sample_catalog_minimal, test_utils.CATALOGS_DIR) element_path = ElementPath('catalog.metadata.*') catalog_element = Element(sample_catalog_minimal) with pytest.raises(err.TrestleError): Add.add(element_path, catalog_element, False) element_path = ElementPath('catalog.metadata.title') with pytest.raises(err.TrestleError): Add.add(element_path, catalog_element, False) element_path = ElementPath('catalog.metadata.bad_path') with pytest.raises(err.TrestleError): Add.add(element_path, catalog_element, False)
def test_run(tmp_dir, sample_catalog_minimal): """Test _run for AddCmd.""" # expected catalog after add of Responsible-Party file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH, 'minimal_catalog_roles_double_rp.json') expected_catalog_roles2_rp = Catalog.oscal_read(file_path) content_type = FileContentType.YAML catalog_def_dir, catalog_def_file = test_utils.prepare_trestle_project_dir( tmp_dir, content_type, sample_catalog_minimal, test_utils.CATALOGS_DIR) testargs = [ 'trestle', 'add', '-f', str(catalog_def_file), '-e', 'catalog.metadata.roles, catalog.metadata.roles, catalog.metadata.responsible-parties' ] with patch.object(sys, 'argv', testargs): Trestle().run() actual_catalog = Catalog.oscal_read(catalog_def_file) assert expected_catalog_roles2_rp == actual_catalog
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