def generate_complex_catalog(stem: str = '') -> cat.Catalog: """Generate a complex and deep catalog for testing.""" group_a = generators.generate_sample_model(cat.Group, True) group_a.id = f'{stem}a' group_a.controls = generate_control_list(group_a.id, 4) group_b = generators.generate_sample_model(cat.Group, True) group_b.id = f'{stem}b' group_b.controls = generate_control_list(group_b.id, 3) group_b.controls[2].controls = generate_control_list(f'{group_b.id}-2', 3) group_ba = generators.generate_sample_model(cat.Group, True) group_ba.id = f'{stem}ba' group_ba.controls = generate_control_list(group_ba.id, 2) group_b.groups = [group_ba] catalog = generators.generate_sample_model(cat.Catalog, True) catalog.controls = generate_control_list(f'{stem}cat', 3) catalog.params = generate_param_list(f'{stem}parm', 3) test_control = generators.generate_sample_model(cat.Control, False) test_control.id = f'{stem}test-1' test_control.params = [common.Parameter(id=f'{test_control.id}_prm_1', values=['Default', 'Values'])] test_control.parts = [ common.Part( id=f'{test_control.id}_smt', name='statement', prose='Statement with no parts. Prose with param value {{ insert: param, test-1_prm_1 }}' ) ] catalog.controls.append(test_control) catalog.groups = [group_a, group_b] return catalog
def test_managed_write(tmp_trestle_dir: pathlib.Path) -> None: """Test model write.""" # generate catalog data and import catalog_data = generators.generate_sample_model(cat.Catalog) repo = Repository(tmp_trestle_dir) managed = repo.import_model(catalog_data, 'imported') # generate another catalog data for writing catalog_data = generators.generate_sample_model(cat.Catalog) success = managed.write(catalog_data) assert success
def test_get_all_sample_models() -> None: """Test we can get all models which exist.""" pkgpath = os.path.dirname(oscal.__file__) for _, name, _ in pkgutil.iter_modules([pkgpath]): __import__(f'trestle.oscal.{name}') clsmembers = inspect.getmembers(sys.modules[f'trestle.oscal.{name}'], inspect.isclass) for _, oscal_cls in clsmembers: # This removes some enums and other objects. if issubclass(oscal_cls, OscalBaseModel): gens.generate_sample_model(oscal_cls)
def sample_component_definition(): """Return a valid ComponentDefinition object with some contents.""" # one component has no properties - the other has two def_comp1: DefinedComponent = gens.generate_sample_model(DefinedComponent) def_comp2: DefinedComponent = gens.generate_sample_model(DefinedComponent) prop_1 = gens.generate_sample_model(common.Property) prop_2 = gens.generate_sample_model(common.Property) def_comp2.props = [prop_1, prop_2] comp_def: ComponentDefinition = gens.generate_sample_model( ComponentDefinition) comp_def.components = [def_comp1, def_comp2] return comp_def
def test_managed_write_invalid_top_model( tmp_trestle_dir: pathlib.Path) -> None: """Invalid top level model while writing.""" # generate catalog data and import catalog_data = generators.generate_sample_model(cat.Catalog) repo = Repository(tmp_trestle_dir) managed = repo.import_model(catalog_data, 'imported') # generate another catalog data for writing catalog_data = generators.generate_sample_model(oscal.catalog.Group) with pytest.raises(TrestleError, match='not a top level model'): managed.write(catalog_data)
def test_validate_dup_uuids( sample_component_definition: ComponentDefinition) -> None: """Test validation of comp def with duplicate uuids.""" args = argparse.Namespace(mode=const.VAL_MODE_ALL) validator = validator_factory.get(args) # confirm the comp_def is valid assert validator.model_is_valid(sample_component_definition) # force two components to have same uuid and confirm invalid sample_component_definition.components[ 1].uuid = sample_component_definition.components[0].uuid assert not validator.model_is_valid(sample_component_definition) # restore uuid to unique value and confirm it is valid again sample_component_definition.components[1].uuid = str(uuid4()) assert validator.model_is_valid(sample_component_definition) # add a control implementation to one of the components and confirm valid control_imp: ControlImplementation = generate_sample_model( ControlImplementation) sample_component_definition.components[1].control_implementations = [ control_imp ] assert validator.model_is_valid(sample_component_definition) # force the control implementation to have same uuid as the first component and confirm invalid sample_component_definition.components[1].control_implementations[ 0].uuid = sample_component_definition.components[0].uuid assert not validator.model_is_valid(sample_component_definition)
def test_control_with_components() -> None: """Test loading and parsing of implementated reqs with components.""" control_path = pathlib.Path( 'tests/data/author/controls/control_with_components.md').resolve() comp_prose_dict, _ = ControlIOReader.read_all_implementation_prose_and_header( control_path) assert len(comp_prose_dict.keys()) == 3 assert len(comp_prose_dict['This System'].keys()) == 3 assert len(comp_prose_dict['Trestle Component'].keys()) == 1 assert len(comp_prose_dict['Fancy Thing'].keys()) == 2 assert comp_prose_dict['Fancy Thing']['a.'] == [ 'Text for fancy thing component' ] # need to build the needed components so they can be referenced comp_dict = {} for comp_name in comp_prose_dict.keys(): comp = gens.generate_sample_model(ossp.SystemComponent) comp.title = comp_name comp_dict[comp_name] = comp # confirm that the header content was inserted into the props of the imp_req imp_req = ControlIOReader.read_implemented_requirement( control_path, comp_dict) assert len(imp_req.props) == 12 assert len(imp_req.statements) == 3 assert len(imp_req.statements[0].by_components) == 3
def test_import_profile_with_optional_added(tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPatch) -> None: """Create profile, add modify to it, and import.""" rand_str = ''.join(random.choice(string.ascii_letters) for x in range(16)) profile_file = f'{tmp_trestle_dir.parent}/{rand_str}.json' # create generic profile profile_data = generators.generate_sample_model( trestle.oscal.profile.Profile) # create special parameter and add it to profile set_parameter = SetParameter(param_id='my_param', depends_on='my_depends') modify = Modify(set_parameters=[set_parameter]) profile_data.modify = modify # write it to place outside trestle directory profile_data.oscal_write(pathlib.Path(profile_file)) # now do actual import into trestle directory with name 'imported' test_args = f'trestle import -f {profile_file} -o imported'.split() monkeypatch.setattr(sys, 'argv', test_args) rc = Trestle().run() assert rc == 0 # then do a direct read of it and confirm our parameter is there profile_path = tmp_trestle_dir / 'profiles/imported/profile.json' profile: Profile = Profile.oscal_read(profile_path) params = profile.modify.set_parameters assert params assert len(params) == 1 assert params[0].param_id == 'my_param' assert params[0].depends_on == 'my_depends'
def sample_catalog_rich_controls(): """Return a catalog with controls in groups and in the catalog itself.""" catalog_obj = gens.generate_sample_model(cat.Catalog) param_0 = common.Parameter( id='param_0', values=[common.ParameterValue(__root__='param_0_val')]) param_1 = common.Parameter( id='param_1', values=[common.ParameterValue(__root__='param_1_val')]) control_a = cat.Control(id='control_a', title='this is control a', params=[param_0, param_1]) control_b = cat.Control(id='control_b', title='this is control b') group = cat.Group(id='xy', title='The xy control group', controls=[control_a, control_b]) catalog_obj.groups = [group] part = common.Part(id='cpart', name='name.c.part') control_c = cat.Control(id='control_c', title='this is control c', parts=[part]) control_d = cat.Control(id='control_d', title='this is control d') control_d1 = cat.Control(id='control_d1', title='this is control d1') control_d.controls = [control_d1] catalog_obj.controls = [control_c, control_d] return catalog_obj
def test_managed_oscal(tmp_trestle_dir: pathlib.Path) -> None: """Test creating Managed OSCAL object.""" # generate catalog data and import catalog_data = generators.generate_sample_model(cat.Catalog) repo = Repository(tmp_trestle_dir) managed = repo.import_model(catalog_data, 'imported') assert managed.model_dir == tmp_trestle_dir / 'catalogs' / 'imported'
def test_validate(tmp_trestle_dir: pathlib.Path) -> None: """Test validate model.""" # create a model catalog_data = generators.generate_sample_model(cat.Catalog) repo = Repository(tmp_trestle_dir) repo.import_model(catalog_data, 'imported') success = repo.validate_model(cat.Catalog, 'imported') assert success
def test_get(tmp_trestle_dir: pathlib.Path) -> None: """Test get model.""" # create a model catalog_data = generators.generate_sample_model(cat.Catalog) repo = Repository(tmp_trestle_dir) repo.import_model(catalog_data, 'imported') managed_oscal = repo.get_model(cat.Catalog, 'imported') assert managed_oscal._model_name == 'imported'
def test_managed_validate(tmp_trestle_dir: pathlib.Path) -> None: """Test model validate.""" # generate catalog data and import catalog_data = generators.generate_sample_model(cat.Catalog) repo = Repository(tmp_trestle_dir) managed = repo.import_model(catalog_data, 'imported') success = managed.validate() assert success
def test_import_invalid_top_model(tmp_trestle_dir: pathlib.Path) -> None: """Invalid top model.""" # try to import Metadata metadata = generators.generate_sample_model(oscal.common.Metadata) repo = Repository(tmp_trestle_dir) with pytest.raises(TrestleError, match='not a top level model'): repo.import_model(metadata, 'imported')
def test_managed_read(tmp_trestle_dir: pathlib.Path) -> None: """Test model read.""" # generate catalog data and import catalog_data = generators.generate_sample_model(cat.Catalog) repo = Repository(tmp_trestle_dir) managed = repo.import_model(catalog_data, 'imported') model = managed.read() assert model.uuid == catalog_data.uuid
def create_object(cls, model_alias: str, object_type: Type[TLO], args: argparse.Namespace) -> int: """Create a top level OSCAL object within the trestle directory, leveraging functionality in add.""" log.set_log_level_from_args(args) trestle_root = fs.get_trestle_project_root(Path.cwd()) if not trestle_root: logger.error( f'Current working directory {Path.cwd()} is not with a trestle project.' ) return 1 plural_path: str # Cater to POAM if model_alias[-1] == 's': plural_path = model_alias else: plural_path = model_alias + 's' desired_model_dir = trestle_root / plural_path / args.name desired_model_path = desired_model_dir / (model_alias + '.' + args.extension) if desired_model_path.exists(): logger.error( f'OSCAL file to be created here: {desired_model_path} exists.') logger.error('Aborting trestle create.') return 1 # Create sample model. sample_model = generators.generate_sample_model(object_type) # Presuming top level level model not sure how to do the typing for this. sample_model.metadata.title = f'Generic {model_alias} created by trestle.' # type: ignore sample_model.metadata.last_modified = datetime.now().astimezone() sample_model.metadata.oscal_version = trestle.oscal.OSCAL_VERSION sample_model.metadata.version = '0.0.0' top_element = Element(sample_model, model_alias) create_action = CreatePathAction(desired_model_path.absolute(), True) write_action = WriteFileAction( desired_model_path.absolute(), top_element, FileContentType.to_content_type(desired_model_path.suffix)) # create a plan to write the directory and file. try: create_plan = Plan() create_plan.add_action(create_action) create_plan.add_action(write_action) create_plan.simulate() create_plan.execute() return 0 except Exception as e: logger.error( 'Unknown error executing trestle create operations. Rolling back.' ) logger.debug(e) return 1
def test_delete(tmp_trestle_dir: pathlib.Path) -> None: """Test delete model.""" # create a model catalog_data = generators.generate_sample_model(cat.Catalog) repo = Repository(tmp_trestle_dir) repo.import_model(catalog_data, 'imported') # created model is 'dist' folder also repo.assemble_model(cat.Catalog, 'imported') success = repo.delete_model(cat.Catalog, 'imported') assert success
def test_generate_sample_model() -> None: """Test utils method generate_sample_model.""" # Create the expected catalog first expected_ctlg_dict = { 'uuid': 'ea784488-49a1-4ee5-9830-38058c7c10a4', 'metadata': { 'title': 'REPLACE_ME', 'last-modified': '2020-10-21T06:52:10.387+00:00', 'version': 'REPLACE_ME', 'oscal-version': oscal.OSCAL_VERSION } } expected_ctlg = catalog.Catalog(**expected_ctlg_dict) actual_ctlg = gens.generate_sample_model(catalog.Catalog) # Check if uuid is valid, then change to uuid of expected catalog, as newly generated # uuids will always be different assert is_valid_uuid(actual_ctlg.uuid) actual_ctlg.uuid = expected_ctlg.uuid # Check if last-modified datetime is of type datetime, and then equate in actual and expected assert type(actual_ctlg.metadata) is common.Metadata actual_ctlg.metadata.last_modified = expected_ctlg.metadata.last_modified # Check that expected generated catalog is now same a actual catalog assert expected_ctlg == actual_ctlg # Test list type models expected_role = common.Role(**{'id': 'REPLACE_ME', 'title': 'REPLACE_ME'}) list_role = gens.generate_sample_model(List[common.Role]) assert type(list_role) is list actual_role = list_role[0] assert expected_role == actual_role # Test dict type models if False: party_uuid = common.PartyUuid(__root__=const.SAMPLE_UUID_STR) expected_rp = {'role_id': 'REPLACE_ME', 'party-uuids': [party_uuid]} expected_rp = common.ResponsibleParty(**expected_rp) expected_rp_dict = {'REPLACE_ME': expected_rp} actual_rp_dict = gens.generate_sample_model( Dict[str, common.ResponsibleParty]) assert type(actual_rp_dict) is dict assert expected_rp_dict == actual_rp_dict
def test_managed_file_not_exist(tmp_trestle_dir: pathlib.Path) -> None: """Test model file does not exist while creating a Managed OSCAL object.""" # generate catalog data and import catalog_data = generators.generate_sample_model(cat.Catalog) repo = Repository(tmp_trestle_dir) managed = repo.import_model(catalog_data, 'imported') # delete file managed.filepath.unlink() with pytest.raises(TrestleError, match=r'Model file .* does not exist'): ManagedOSCAL(tmp_trestle_dir, cat.Catalog, 'imported')
def test_assemble(tmp_trestle_dir: pathlib.Path) -> None: """Test assemble model.""" # create a model catalog_data = generators.generate_sample_model(cat.Catalog) repo = Repository(tmp_trestle_dir) repo.import_model(catalog_data, 'imported') success = repo.assemble_model(cat.Catalog, 'imported') assert success dist_model_path = pathlib.Path(tmp_trestle_dir, 'dist', 'catalogs', 'imported.json') assert dist_model_path.exists()
def test_import_model_exists(tmp_trestle_dir: pathlib.Path) -> None: """Model already exists.""" # Generate sample catalog model catalog_data = generators.generate_sample_model(cat.Catalog) repo = Repository(tmp_trestle_dir) managed_oscal = repo.import_model(catalog_data, 'imported') assert managed_oscal.filepath.exists() with pytest.raises(TrestleError, match=r'OSCAL file .* exists'): repo.import_model(catalog_data, 'imported')
def test_import(tmp_trestle_dir: pathlib.Path) -> None: """Test import.""" # Generate sample catalog model catalog_data = generators.generate_sample_model(cat.Catalog) repo = Repository(tmp_trestle_dir) managed_oscal = repo.import_model(catalog_data, 'imported') assert managed_oscal._root_dir == tmp_trestle_dir assert managed_oscal._model_name == 'imported' assert managed_oscal._model_type == catalog_data.__class__ assert managed_oscal.filepath.exists()
def test_generate_sample_model(): """Test utils method generate_sample_model.""" # Create the expected catalog first expected_ctlg_dict = { 'uuid': 'ea784488-49a1-4ee5-9830-38058c7c10a4', 'metadata': { 'title': 'REPLACE_ME', 'last-modified': '2020-10-21T06:52:10.387+00:00', 'version': 'REPLACE_ME', 'oscal-version': 'REPLACE_ME' } } expected_ctlg = catalog.Catalog(**expected_ctlg_dict) actual_ctlg = gens.generate_sample_model(catalog.Catalog) # Check if uuid is valid, then change to uuid of expected catalog, as newly generated # uuids will always be different assert is_valid_uuid(actual_ctlg.uuid) actual_ctlg.uuid = expected_ctlg.uuid # Check if last-modified datetime is of type datetime, and then equate in actual and expected assert type(actual_ctlg.metadata.last_modified) is catalog.LastModified actual_ctlg.metadata.last_modified = expected_ctlg.metadata.last_modified # Check that expected generated catalog is now same a actual catalog assert expected_ctlg == actual_ctlg # Test list type models expected_role = catalog.Role(**{'id': 'REPLACE_ME', 'title': 'REPLACE_ME'}) list_role = gens.generate_sample_model(typing.List[catalog.Role]) assert type(list_role) is list actual_role = list_role[0] assert expected_role == actual_role # Test dict type models expected_rp = {'party-uuids': ['00000000-0000-4000-8000-000000000000']} expected_rp = catalog.ResponsibleParty(**expected_rp) expected_rp_dict = {'REPLACE_ME': expected_rp} actual_rp_dict = gens.generate_sample_model( typing.Dict[str, catalog.ResponsibleParty]) assert type(actual_rp_dict) is dict assert expected_rp_dict == actual_rp_dict
def generate_param_list(label: str, count: int) -> List[cat.Control]: """Generate a list of params with indexed names.""" params: List[common.Parameter] = [] for ii in range(count): param = generators.generate_sample_model(common.Parameter, True) param.id = f'{label}-{ii + 1}' param.label = f'label-{param.id}' param.props[0].name = f'name-{param.id}' param.props[0].value = f'value-{param.id}' param.guidelines[0].prose = f'prose-{param.id}' params.append(param) return params
def test_import_non_top_level_element(tmp_trestle_dir: pathlib.Path) -> None: """Test for expected fail to import non-top level element, e.g., groups.""" # Input file, catalog: rand_str = ''.join(random.choice(string.ascii_letters) for x in range(16)) groups_file = f'{tmp_trestle_dir.parent}/{rand_str}.json' groups_data = generators.generate_sample_model(trestle.oscal.catalog.Group) groups_data.oscal_write(pathlib.Path(groups_file)) args = argparse.Namespace(file=groups_file, output='imported', verbose=True) i = importcmd.ImportCmd() rc = i._run(args) assert rc == 1
def test_list(tmp_trestle_dir: pathlib.Path) -> None: """Test list models.""" # 1. Empty list repo = Repository(tmp_trestle_dir) model_list = repo.list_models(cat.Catalog) assert len(model_list) == 0 # model exists catalog_data = generators.generate_sample_model(cat.Catalog) repo.import_model(catalog_data, 'imported') model_list = repo.list_models(cat.Catalog) assert len(model_list) == 1 assert 'imported' in model_list
def test_profile_resolver_merge(sample_catalog_rich_controls: cat.Catalog) -> None: """Test profile resolver merge.""" profile = gens.generate_sample_model(prof.Profile) method = prof.Method.merge combine = prof.Combine(method=method) profile.merge = prof.Merge(combine=combine) merge = Merge(profile) # merge into empty catalog merged = gens.generate_sample_model(cat.Catalog) new_merged = merge._merge_catalog(merged, sample_catalog_rich_controls) catalog_interface = CatalogInterface(new_merged) assert catalog_interface.get_count_of_controls_in_catalog(True) == 5 # add part to first control and merge, then make sure it is there part = com.Part(name='foo', title='added part') control_id = sample_catalog_rich_controls.controls[0].id cat_with_added_part = copy.deepcopy(sample_catalog_rich_controls) cat_with_added_part.controls[0].parts.append(part) final_merged = merge._merge_catalog(sample_catalog_rich_controls, cat_with_added_part) catalog_interface = CatalogInterface(final_merged) assert catalog_interface.get_count_of_controls_in_catalog(True) == 5 assert catalog_interface.get_control(control_id).parts[-1].name == 'foo' # add part to first control and merge but with use-first. The part should not be there at end. method = prof.Method.use_first combine = prof.Combine(method=method) profile.merge = prof.Merge(combine=combine) merge = Merge(profile) final_merged = merge._merge_catalog(sample_catalog_rich_controls, cat_with_added_part) catalog_interface = CatalogInterface(final_merged) assert catalog_interface.get_count_of_controls_in_catalog(True) == 5 assert len(catalog_interface.get_control(control_id).parts) == 1 # now force a merge with keep profile.merge = None merge_keep = Merge(profile) merged_keep = merge_keep._merge_catalog(new_merged, sample_catalog_rich_controls) assert CatalogInterface(merged_keep).get_count_of_controls_in_catalog(True) == 10
def test_import_run(tmp_trestle_dir: pathlib.Path) -> None: """Test successful _run() on valid and invalid.""" rand_str = ''.join(random.choice(string.ascii_letters) for x in range(16)) catalog_file = f'{tmp_trestle_dir.parent}/{rand_str}.json' catalog_data = generators.generate_sample_model( trestle.oscal.catalog.Catalog) catalog_data.oscal_write(pathlib.Path(catalog_file)) i = importcmd.ImportCmd() args = argparse.Namespace(file=catalog_file, output='imported', verbose=True) rc = i._run(args) assert rc == 0
def test_import_root_key_found(tmp_trestle_dir: pathlib.Path) -> None: """Test root key is found.""" rand_str = ''.join(random.choice(string.ascii_letters) for x in range(16)) catalog_file = f'{tmp_trestle_dir.parent}/{rand_str}.json' catalog_data = generators.generate_sample_model(Catalog) catalog_data.oscal_write(pathlib.Path(catalog_file)) args = argparse.Namespace(trestle_root=tmp_trestle_dir, file=catalog_file, output='catalog', verbose=1, regenerate=False) i = importcmd.ImportCmd() rc = i._run(args) assert rc == 0
def test_import_cmd(tmp_trestle_dir: pathlib.Path) -> None: """Happy path test at the cli level.""" # 1. Input file, profile: rand_str = ''.join(random.choice(string.ascii_letters) for x in range(16)) profile_file = f'{tmp_trestle_dir.parent}/{rand_str}.json' profile_data = generators.generate_sample_model( trestle.oscal.profile.Profile) profile_data.oscal_write(pathlib.Path(profile_file)) # 2. Input file, target: rand_str = ''.join(random.choice(string.ascii_letters) for x in range(16)) target_file = f'{tmp_trestle_dir.parent}/{rand_str}.json' target_data = generators.generate_sample_model( trestle.oscal.target.TargetDefinition) target_data.oscal_write(pathlib.Path(target_file)) # Test 1 test_args = f'trestle import -f {profile_file} -o imported'.split() with patch.object(sys, 'argv', test_args): rc = Trestle().run() assert rc == 0 # Test 2 test_args = f'trestle import -f {target_file} -o imported'.split() with patch.object(sys, 'argv', test_args): rc = Trestle().run() assert rc == 0