Skip to content

Commit

Permalink
fix: Raise error if duplicate parts are found in the control statement (
Browse files Browse the repository at this point in the history
#1351)

* fix: Raise error if duplicate parts are found in statement

Signed-off-by: Ekaterina Nikonova <enikonovad@gmail.com>

* fix: Make lint happy

Signed-off-by: Ekaterina Nikonova <enikonovad@gmail.com>

* Remove unnecessary logging

Signed-off-by: Ekaterina Nikonova <enikonovad@gmail.com>

---------

Signed-off-by: Ekaterina Nikonova <enikonovad@gmail.com>
  • Loading branch information
enikonovad committed Apr 17, 2023
1 parent a0b1797 commit 74bd4f5
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 6 deletions.
32 changes: 32 additions & 0 deletions tests/trestle/core/commands/author/catalog_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -661,3 +661,35 @@ def test_catalog_multiline_statement(tmp_trestle_dir: pathlib.Path, monkeypatch:
assert catalog.groups[0].controls[1].parts[0].parts[
0
].prose == 'Define and document the types of accounts allowed and specifically prohibited for use within the system;' # noqa E501


def test_catalog_duplicate_parts_statement(tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPatch, capsys) -> None:
"""Test catalog-assemble with duplicate parts for control statement."""
catalog = cat.Catalog.oscal_read(test_utils.JSON_TEST_DATA_PATH / test_utils.SIMPLIFIED_NIST_CATALOG_NAME)
ModelUtils.save_top_level_model(catalog, tmp_trestle_dir, 'my_catalog', FileContentType.JSON)

catalog_generate = 'trestle author catalog-generate -n my_catalog -o md_catalog'
test_utils.execute_command_and_assert(catalog_generate, 0, monkeypatch)
md_path = tmp_trestle_dir / 'md_catalog/ac/ac-2.md'
assert md_path.exists()

control_statement_prose_with_parts = """The organization:
- \[a\] Part A
- \[a\] Part A Duplicate.
- \[a.1\] Documents 1
- \[a.2\] Documents 2
- \[a.2.1\] SubDocuments 1
- \[a.2.2\] SubDocuments 2
- \[b\] Part 2.
"""

file_utils.insert_text_in_file(md_path, '## Control Statement', control_statement_prose_with_parts)

catalog_assemble = 'trestle author catalog-assemble -o my_catalog -m md_catalog'
test_utils.execute_command_and_assert(catalog_assemble, 1, monkeypatch)

_, error = capsys.readouterr()
assert 'Duplicate part id ac-2_smt.a' in error
12 changes: 6 additions & 6 deletions trestle/core/markdown/base_markdown_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def get_all_headers_for_level(self, level: int) -> Iterable[str]:
def get_node_for_key(self, key: str, strict_matching: bool = True) -> Optional[BaseMarkdownNode]:
"""Return a first node for the given key, substring matching is supported. The method is case insensitive."""
if not strict_matching:
if not any([key.lower() in el.lower() for el in self.content.subnodes_keys]):
if not any(key.lower() in el.lower() for el in self.content.subnodes_keys):
return None
elif len(as_filtered_list(self.content.subnodes_keys, lambda el: key.lower() in el.lower())) > 1:
logger.warning(f'Multiple nodes for {key} were found, only the first one will be returned.')
Expand Down Expand Up @@ -99,7 +99,7 @@ def get_all_nodes_for_keys(
Returns: List of found markdown nodes
"""
if not strict_matching:
if not any([key in el for el in self.content.subnodes_keys for key in keys]):
if not any(key in el for el in self.content.subnodes_keys for key in keys):
return []
elif not set(keys).intersection(self.content.subnodes_keys):
return []
Expand Down Expand Up @@ -256,10 +256,10 @@ def _rec_traverse(self, node: BaseMarkdownNode, key: str, strict_matching: bool)
"""
if key.lower() == node.key.lower() or (not strict_matching and key.lower() in node.key.lower()):
return node
if (not strict_matching and any([key.lower() in el.lower()
for el in node.content.subnodes_keys])) or (key.lower() in [
el.lower() for el in node.content.subnodes_keys
]):
if (not strict_matching and any(key.lower() in el.lower()
for el in node.content.subnodes_keys)) or (key.lower() in [
el.lower() for el in node.content.subnodes_keys
]):
for subnode in node.subnodes:
matched_node = self._rec_traverse(subnode, key, strict_matching)
if matched_node is not None:
Expand Down
5 changes: 5 additions & 0 deletions trestle/core/markdown/control_markdown_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,11 @@ def _read_parts(self, indent: int, ii: int, lines: List[str], parent_id: str,
name = 'objective' if id_.find('_obj') > 0 else 'item'
prop = common.Property(name='label', value=id_text)
part = common.Part(name=name, id=id_, prose=prose, props=[prop])
if id_ in [p.id for p in parts]:
raise TrestleError(
f'Duplicate part id {id_} is found in markdown '
f'{tree_context.control_id}. Please correct the part label in line {line}.'
)
parts.append(part)
ii += 1
elif new_indent > indent:
Expand Down
1 change: 1 addition & 0 deletions trestle/core/markdown/markdown_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def process_control_markdown(
_ = self.render_gfm_to_html(markdown_wo_header)

lines = markdown_wo_header.split('\n')
tree_context.reset()
tree_context.section_to_part_name_map = section_to_part_name_map
tree_context.part_label_to_id_map = part_label_to_id_map
tree = ControlMarkdownNode.build_tree_from_markdown(lines)
Expand Down

0 comments on commit 74bd4f5

Please sign in to comment.