diff --git a/tests/trestle/tasks/csv_to_oscal_cd_test.py b/tests/trestle/tasks/csv_to_oscal_cd_test.py index 78a264a13..48c9da994 100644 --- a/tests/trestle/tasks/csv_to_oscal_cd_test.py +++ b/tests/trestle/tasks/csv_to_oscal_cd_test.py @@ -1138,3 +1138,69 @@ def test_execute_validation(tmp_path: pathlib.Path) -> None: assert component.props[2].name == 'Check_Description' assert component.props[2].value == 'validation-check-description' assert len(component.control_implementations) == 0 + + +def test_row_property_builder(tmp_path): + """Test row property builder.""" + # valid + prop = csv_to_oscal_cd.row_property_builder( + row=0, + name='name', + value='value', + ns='https://www.ibm.com', + class_='class', + remarks='remarks', + ) + assert prop + # missing name + try: + prop = csv_to_oscal_cd.row_property_builder( + row=0, + name=None, + value='value', + ns='https://www.ibm.com', + class_='class', + remarks='remarks', + ) + raise AssertionError('missing name OK?') + except Exception: + assert prop + # missing value + try: + prop = csv_to_oscal_cd.row_property_builder( + row=0, + name='name', + value=None, + ns='https://www.ibm.com', + class_='class', + remarks='remarks', + ) + raise AssertionError('missing value OK?') + except Exception: + assert prop + # invalid ns + try: + prop = csv_to_oscal_cd.row_property_builder( + row=0, + name='name', + value='value', + ns='foobar', + class_='class', + remarks='remarks', + ) + raise AssertionError('invalid ns OK?') + except Exception: + assert prop + # invalid class + try: + prop = csv_to_oscal_cd.row_property_builder( + row=0, + name='name', + value='value', + ns='https://www.ibm.com', + class_='\n', + remarks='remarks', + ) + raise AssertionError('invalid class OK?') + except Exception: + assert prop diff --git a/trestle/tasks/csv_to_oscal_cd.py b/trestle/tasks/csv_to_oscal_cd.py index 39a07ca7d..1a0a11c7f 100644 --- a/trestle/tasks/csv_to_oscal_cd.py +++ b/trestle/tasks/csv_to_oscal_cd.py @@ -82,6 +82,65 @@ def derive_part_id(control_mapping: str) -> str: return rval +def etype(target: str) -> str: + """Get etype.""" + if target: + return 'invalid' + else: + return 'missing' + + +def row_property_builder(row: int, name: str, value, ns: str, class_: str, remarks: str) -> Property: + """Row property builder.""" + # name + try: + Property( + name=name, + value='value', + ) + except Exception: + text = f'property for row: {row} name: {name} is {etype(name)}' + raise RuntimeError(text) + # value + try: + Property( + name=name, + value=value, + ) + except Exception: + text = f'property for row: {row} value: {value} is {etype(value)}' + raise RuntimeError(text) + # ns + try: + Property( + name=name, + value=value, + ns=ns, + ) + except Exception: + text = f'property for row: {row} ns: {ns} is {etype(ns)}' + raise RuntimeError(text) + # class + try: + Property( + name=name, + value=value, + class_=class_, + ) + except Exception: + text = f'property for row: {row} class: {class_} is {etype(class_)}' + raise RuntimeError(text) + # prop + prop = Property( + name=name, + value=value, + ns=ns, + class_=class_, + remarks=remarks, + ) + return prop + + class CsvToOscalComponentDefinition(TaskBase): """ Task to create OSCAL ComponentDefinition json. @@ -911,18 +970,15 @@ def __init__(self, row_number: int, rule_set: str) -> None: def add_prop(self, name: str, value: str, ns: str, class_: str) -> None: """Add prop.""" if value is not None and len(value): - try: - prop = Property( - name=name, - value=value, - ns=ns, - class_=class_, - remarks=self._rule_set, - ) - self._props[name] = prop - except Exception: - text = f'Invalid property name: {name} value: {value} remarks: {self._rule_set}' - raise RuntimeError(text) + prop = row_property_builder( + row=self._row_number, + name=name, + value=value, + ns=ns, + class_=class_, + remarks=self._rule_set, + ) + self._props[name] = prop def get_props(self) -> List[Property]: """Get props.""" @@ -1438,8 +1494,6 @@ def __init__(self, csv_path: pathlib.Path) -> None: self._csv_controls_map = {} self._csv_profile_list = [] for row_num, row in self.row_generator(): - if self._is_no_control(row): - continue self._check_row_minimum_requirements(row_num, row) component_title = self.get_row_value(row, f'{COMPONENT_TITLE}') component_type = self.get_row_value(row, f'{COMPONENT_TYPE}') @@ -1454,7 +1508,7 @@ def __init__(self, csv_path: pathlib.Path) -> None: logger.debug(f'csv-rules: {key} {self._csv_rules_map[key][0]}') # set parameters, by component source = self.get_row_value(row, PROFILE_SOURCE) - if source not in self._csv_profile_list: + if source and source not in self._csv_profile_list: self._csv_profile_list.append(source) description = self.get_row_value(row, PROFILE_DESCRIPTION) param_id = self.get_row_value(row, PARAMETER_ID) @@ -1489,6 +1543,8 @@ def row_generator(self) -> Generator[Union[int, Iterator[List[str]]], None, None index += 1 if index < 3: continue + if self._is_no_control(row): + continue logger.debug(f'row_gen: {index} {row}') yield index, row