Esempio n. 1
0
def test_contextual_get_singular_alias(tmp_path: pathlib.Path,
                                       keep_cwd: pathlib.Path) -> None:
    """Test get_singular_alias in contextual mode."""
    # Contextual model tests
    create_sample_catalog_project(tmp_path)
    catalogs_dir = tmp_path.resolve() / 'catalogs'
    mycatalog_dir = catalogs_dir / 'mycatalog'
    catalog_dir = mycatalog_dir / 'catalog'
    metadata_dir = catalog_dir / 'metadata'
    groups_dir = catalog_dir / 'groups'
    group_dir = groups_dir / f'00000{const.IDX_SEP}group'

    rel_dir = mycatalog_dir.relative_to(tmp_path)
    assert 'responsible-party' == ModelUtils.get_singular_alias(
        'catalog.metadata.responsible-parties', rel_dir)
    # Both should work to deal with the case back-matter is already split from the catalog in a separate file
    assert 'resource' == ModelUtils.get_singular_alias(
        'catalog.back-matter.resources', rel_dir)
    assert 'resource' == ModelUtils.get_singular_alias('back-matter.resources',
                                                       rel_dir)

    rel_dir = metadata_dir.relative_to(tmp_path)
    with pytest.raises(TrestleError):
        ModelUtils.get_singular_alias('metadata.roles')
    alias = ModelUtils.get_singular_alias('metadata.roles', rel_dir)
    assert alias == 'role'
    assert 'responsible-party' == ModelUtils.get_singular_alias(
        'metadata.responsible-parties.*', rel_dir)

    assert 'property' == ModelUtils.get_singular_alias(
        'metadata.responsible-parties.*.props', rel_dir)

    rel_dir = groups_dir.relative_to(tmp_path)
    assert 'control' == ModelUtils.get_singular_alias(
        'groups.*.controls.*.controls', rel_dir)

    rel_dir = group_dir.relative_to(tmp_path)
    assert 'control' == ModelUtils.get_singular_alias(
        'group.controls.*.controls', rel_dir)
Esempio n. 2
0
def test_get_singular_alias() -> None:
    """Test get_singular_alias function."""
    assert ModelUtils.get_singular_alias(alias_path='catalog') == 'catalog'

    # Not fullpath. It should be 'catalog.metadata' instead
    with pytest.raises(TrestleError):
        ModelUtils.get_singular_alias(alias_path='metadata.something')

    # Invalid alias_path
    with pytest.raises(TrestleError):
        ModelUtils.get_singular_alias(alias_path='invalid')
    # Invalid alias_path
    with pytest.raises(TrestleError):
        ModelUtils.get_singular_alias(alias_path='')

    assert ModelUtils.get_singular_alias(
        alias_path='catalog.metadata.responsible-parties'
    ) == 'responsible-party'
    assert ModelUtils.get_singular_alias(
        alias_path='catalog.metadata.responsible-parties.*.props'
    ) == 'property'
    assert 'responsible-party' == ModelUtils.get_singular_alias(
        alias_path='catalog.metadata.responsible-parties.*')

    assert 'role' == ModelUtils.get_singular_alias(
        alias_path='catalog.metadata.roles')
    assert 'property' == ModelUtils.get_singular_alias(
        alias_path='catalog.metadata.props')

    assert 'control-implementations' == ModelUtils.get_singular_alias(
        alias_path='component-definition.components.control-implementations')
    assert 'control-implementation' == ModelUtils.get_singular_alias(
        alias_path='component-definition.components.*.control-implementations')
    assert 'control-implementation' == ModelUtils.get_singular_alias(
        alias_path='component-definition.components.0.control-implementations')
    # FIXME ideally this should report error
    assert '0' == ModelUtils.get_singular_alias(
        alias_path='component-definition.components.0')

    assert 'control' == ModelUtils.get_singular_alias(
        alias_path='catalog.groups.*.controls.*.controls')
Esempio n. 3
0
def parse_chain(
    model_obj: Union[OscalBaseModel, None],
    path_parts: List[str],
    relative_path: Optional[pathlib.Path] = None
) -> List[ElementPath]:
    """Parse the model chain starting from the beginning.

    Args:
        model_obj: Model to use for inspecting available elements, if available or none
        path_parts: list of string paths to parse including wildcards
        relative_path: Optional relative path (w.r.t trestle project root directory)

    Returns:
        List of ElementPath
    """
    element_paths: List[ElementPath] = []
    sub_model = model_obj
    have_model_to_parse = model_obj is not None

    prev_element_path = None
    latest_path = None
    parent_model = path_parts[0]
    i = 1
    while i < len(path_parts):
        p = path_parts[i]

        # if hit wildcard create element path up to this point
        if p == ElementPath.WILDCARD and len(element_paths) > 0:
            # append wildcard to the latest element path
            latest_path = element_paths.pop()
            if latest_path.get_last() == ElementPath.WILDCARD:
                raise TrestleError(f'Invalid element path with consecutive {ElementPath.WILDCARD}')

            latest_path_str = ElementPath.PATH_SEPARATOR.join([latest_path.to_string(), p])
            element_path = ElementPath(latest_path_str, latest_path.get_parent())
        else:
            # create and append element_path
            # at this point sub_model may be a list of items
            # new element path is needed only if any of the items contains the desired part
            if p != ElementPath.WILDCARD:
                new_attrib = str_utils.dash_to_underscore(p)
                if isinstance(sub_model, list):
                    for item in sub_model:
                        # go into the list and find one with requested part
                        sub_item = getattr(item, new_attrib, None)
                        if sub_item is not None:
                            sub_model = sub_item
                            break
                else:
                    sub_model = getattr(sub_model, new_attrib, None)
            if have_model_to_parse and sub_model is None:
                return element_paths
            p = ElementPath.PATH_SEPARATOR.join([parent_model, p])
            element_path = ElementPath(p, parent_path=prev_element_path)

        # If the path has wildcard and there are more parts later,
        # get the parent model for the alias path
        # If path has wildcard and it does not refer to a list, then there can be nothing after *
        if element_path.get_last() == ElementPath.WILDCARD:
            full_path_str = ElementPath.PATH_SEPARATOR.join(element_path.get_full_path_parts()[:-1])
            parent_model = ModelUtils.get_singular_alias(full_path_str, relative_path)
            # Does wildcard mean we need to inspect the sub_model to determine what can be split off from it?
            # If it has __root__ it may mean it contains a list of objects and should be split as a list
            if isinstance(sub_model, OscalBaseModel):
                root = getattr(sub_model, '__root__', None)
                if root is None or not isinstance(root, list):
                    # Cannot have parts beyond * if it isn't a list
                    if i < len(path_parts) - 1:
                        raise TrestleError(
                            f'Cannot split beyond * when the wildcard does not refer to a list.  Path: {path_parts}'
                        )
                    for key in sub_model.__fields__.keys():
                        # only create element path is item is present in the sub_model
                        if getattr(sub_model, key, None) is None:
                            continue
                        new_alias = str_utils.underscore_to_dash(key)
                        new_path = full_path_str + '.' + new_alias
                        if not split_is_too_fine(new_path, model_obj):
                            # to add parts of an element, need to add two links
                            # prev_element_path may be None, for example catalog.*
                            if prev_element_path is not None:
                                element_paths.append(prev_element_path)
                            element_paths.append(ElementPath(parent_model + '.' + new_alias, latest_path))
                    # Since wildcard is last in the chain when splitting an oscal model we are done
                    return element_paths
        else:
            parent_model = element_path.get_element_name()

        # store values for next cycle
        prev_element_path = element_path
        element_paths.append(element_path)
        i += 1
    return element_paths