diff --git a/tests/trestle/core/commands/author/profile_test.py b/tests/trestle/core/commands/author/profile_test.py index a6e6bc0e0..99633e82d 100644 --- a/tests/trestle/core/commands/author/profile_test.py +++ b/tests/trestle/core/commands/author/profile_test.py @@ -1300,3 +1300,49 @@ def test_profile_generate_assesment_objectives(tmp_trestle_dir: pathlib.Path, mo monkeypatch.setattr(sys, 'argv', test_args) assert Trestle().run() == 0 + + +def test_profile_generate_assemble_param_value_origin(tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPatch) -> None: + """Test the profile markdown generator.""" + _, assembled_prof_dir, _, markdown_path = setup_profile_generate(tmp_trestle_dir, 'simple_test_profile.json') + yaml_header_path = test_utils.YAML_TEST_DATA_PATH / 'good_simple.yaml' + ac_path = markdown_path / 'ac' + + # convert resolved profile catalog to markdown then assemble it after adding an item to a control + # generate, edit, assemble + test_args = f'trestle author profile-generate -n {prof_name} -o {md_name} -rs NeededExtra'.split( # noqa E501 + ) + test_args.extend(['-y', str(yaml_header_path)]) + test_args.extend(['-s', all_sections_str]) + monkeypatch.setattr(sys, 'argv', test_args) + + assert Trestle().run() == 0 + + fc = test_utils.FileChecker(ac_path) + + assert Trestle().run() == 0 + + assert fc.files_unchanged() + + md_path = markdown_path / 'ac' / 'ac-1.md' + assert md_path.exists() + md_api = MarkdownAPI() + header, tree = md_api.processor.process_markdown(md_path) + + assert header + header[const.SET_PARAMS_TAG]['ac-1_prm_1'][const.PARAM_VALUE_ORIGIN] = 'coming from xyz corporate policy' + + md_api.write_markdown_with_header(md_path, header, tree.content.raw_text) + + # assemble based on set_parameters_flag + test_args = f'trestle author profile-assemble -n {prof_name} -m {md_name} -o {assembled_prof_name}'.split() + test_args.append('-sp') + assembled_prof_dir.mkdir() + monkeypatch.setattr(sys, 'argv', test_args) + assert Trestle().run() == 0 + + profile, _ = ModelUtils.load_model_for_class(tmp_trestle_dir, 'my_assembled_prof', + prof.Profile, FileContentType.JSON) + + # grabs first parameter in line and test out the value + assert profile.modify.set_parameters[0].param_value_origin == 'coming from xyz corporate policy' diff --git a/trestle/common/const.py b/trestle/common/const.py index cb87530ca..fa1cfb853 100644 --- a/trestle/common/const.py +++ b/trestle/common/const.py @@ -443,6 +443,8 @@ GUIDELINES = 'guidelines' +PARAM_VALUE_ORIGIN = 'param-value-origin' + LABEL = 'label' SECTIONS_TAG = TRESTLE_TAG + 'sections' diff --git a/trestle/common/model_utils.py b/trestle/common/model_utils.py index 13e93ff5d..3a75a90c7 100644 --- a/trestle/common/model_utils.py +++ b/trestle/common/model_utils.py @@ -631,6 +631,11 @@ def dict_to_parameter(param_dict: Dict[str, Any]) -> common.Parameter: if const.AGGREGATES in param_dict: # removing aggregates as this is prop just informative in markdown param_dict.pop(const.AGGREGATES) + param_value_origin = None + if const.PARAM_VALUE_ORIGIN in param_dict: + param_value_origin = param_dict[const.PARAM_VALUE_ORIGIN] + # removing param-value-origin as this is prop donĀ“t have any value + param_dict.pop(const.PARAM_VALUE_ORIGIN) if const.ALT_IDENTIFIER in param_dict: # removing alt-identifier as this is prop just informative in markdown param_dict.pop(const.ALT_IDENTIFIER) @@ -639,6 +644,7 @@ def dict_to_parameter(param_dict: Dict[str, Any]) -> common.Parameter: param_dict.pop('ns') param = common.Parameter(**param_dict) param.props = none_if_empty(props) + param.param_value_origin = param_value_origin return param @staticmethod diff --git a/trestle/core/catalog/catalog_writer.py b/trestle/core/catalog/catalog_writer.py index 0a632d2ce..59256a3fd 100644 --- a/trestle/core/catalog/catalog_writer.py +++ b/trestle/core/catalog/catalog_writer.py @@ -161,6 +161,7 @@ def _construct_set_parameters_dict( # pull only the values from the actual control dict # all the other elements are from the profile set_param new_dict[const.VALUES] = orig_dict.get(const.VALUES, None) + new_dict[const.PARAM_VALUE_ORIGIN] = None new_dict[const.GUIDELINES] = orig_dict.get(const.GUIDELINES, None) if new_dict[const.VALUES] is None: new_dict.pop(const.VALUES) @@ -197,7 +198,8 @@ def _construct_set_parameters_dict( const.AGGREGATES, const.ALT_IDENTIFIER, const.DISPLAY_NAME, - const.PROFILE_VALUES + const.PROFILE_VALUES, + const.PARAM_VALUE_ORIGIN ) ordered_dict = {k: new_dict[k] for k in key_order if k in new_dict.keys()} set_param_dict[param_id] = ordered_dict diff --git a/trestle/core/commands/author/profile.py b/trestle/core/commands/author/profile.py index c2469cfe6..ede011043 100644 --- a/trestle/core/commands/author/profile.py +++ b/trestle/core/commands/author/profile.py @@ -272,7 +272,8 @@ def _replace_modify_set_params( label=param.label, values=param.values, select=param.select, - props=param.props + props=param.props, + param_value_origin=param.param_value_origin ) ) if profile.modify.set_parameters != new_set_params: diff --git a/trestle/oscal/common.py b/trestle/oscal/common.py index b160993e4..314e3d2b2 100644 --- a/trestle/oscal/common.py +++ b/trestle/oscal/common.py @@ -1626,6 +1626,7 @@ class Config: values: Optional[List[constr(regex=r'^\S(.*\S)?$')]] = Field(None) select: Optional[ParameterSelection] = None remarks: Optional[str] = None + param_value_origin: Optional[str] = None class OriginActor(OscalBaseModel): diff --git a/trestle/oscal/profile.py b/trestle/oscal/profile.py index 4b0bf33d4..24904f475 100644 --- a/trestle/oscal/profile.py +++ b/trestle/oscal/profile.py @@ -106,6 +106,7 @@ class Config: guidelines: Optional[List[common.ParameterGuideline]] = Field(None) values: Optional[List[constr(regex=r'^\S(.*\S)?$')]] = Field(None) select: Optional[common.ParameterSelection] = None + param_value_origin: Optional[str] = Field(None, alias='parameter-value-origin') class Remove(OscalBaseModel):