Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: adds integration test bewtween rules transform, assemble, and r… #61

Merged
merged 2 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions tests/integration/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/python

# Copyright 2023 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Integration tests between different types of tasks classes."""
117 changes: 117 additions & 0 deletions tests/integration/test_rules_transform_workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/usr/bin/python

# Copyright 2023 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

"""Component test the rules transformation workflow."""

import os
import pathlib

from trestle.common.load_validate import load_validate_model_name
from trestle.oscal import component as comp

import trestlebot.const as const
from tests.testutils import setup_for_profile
from trestlebot.tasks.assemble_task import AssembleTask
from trestlebot.tasks.authored.compdef import AuthoredComponentDefinition
from trestlebot.tasks.authored.types import AuthoredType
from trestlebot.tasks.regenerate_task import RegenerateTask
from trestlebot.tasks.rule_transform_task import RuleTransformTask
from trestlebot.transformers.yaml_transformer import ToRulesYAMLTransformer


test_component_definition = "test_component_definition"
test_profile = "simplified_nist_profile"
test_md_path = "md_compdef"


def test_rules_transform_workflow(tmp_trestle_dir: str) -> None:
"""Test the rules transformation workflow for component definitions."""

trestle_root_path = pathlib.Path(tmp_trestle_dir)

# Environment setup and initial rule generation
_ = setup_for_profile(trestle_root_path, test_profile, test_profile)

authored_compdef = AuthoredComponentDefinition(trestle_root=tmp_trestle_dir)
authored_compdef.create_new_default(
profile_name=test_profile,
compdef_name=test_component_definition,
comp_title="Test",
comp_description="Test component definition",
comp_type="service",
)

# Transform
transform = RuleTransformTask(
tmp_trestle_dir, const.RULES_VIEW_DIR, ToRulesYAMLTransformer()
)
transform.execute()

# Load the component definition
compdef: comp.ComponentDefinition
compdef, _ = load_validate_model_name(
trestle_root_path, test_component_definition, comp.ComponentDefinition
)

assert len(compdef.components) == 1
component = compdef.components[0]

assert component.title == "Test"
assert component.description == "Test component definition"
assert component.type == "service"
assert len(component.props) == 24
assert len(component.control_implementations) == 1
assert (
component.control_implementations[0].source
== f"trestle://profiles/{test_profile}/profile.json"
)

last_modified = compdef.metadata.last_modified

# Run regenerate
regenerate = RegenerateTask(
tmp_trestle_dir, AuthoredType.COMPDEF.value, test_md_path
)
regenerate.execute()

assert os.path.exists(
os.path.join(trestle_root_path, test_md_path, test_component_definition)
)

# Run assemble
assemble = AssembleTask(tmp_trestle_dir, AuthoredType.COMPDEF.value, test_md_path)
assemble.execute()

# Load the component definition
compdef, _ = load_validate_model_name(
trestle_root_path, test_component_definition, comp.ComponentDefinition
)

assert len(compdef.components) == 1
component = compdef.components[0]

# Asset last modified is updated, but all expected information is still present
assert compdef.metadata.last_modified > last_modified

assert component.title == "Test"
assert component.description == "Test component definition"
assert component.type == "service"
assert len(component.props) == 24
assert len(component.control_implementations) == 1
assert (
component.control_implementations[0].source
== f"trestle://profiles/{test_profile}/profile.json"
)
14 changes: 14 additions & 0 deletions tests/testutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,3 +245,17 @@ def write_index_json(

with open(file_path, "w") as file:
json.dump(data, file, indent=4)


def replace_string_in_file(file_path: str, old_string: str, new_string: str) -> None:
"""Replace a string in a file."""
# Read the content of the file
with open(file_path, "r") as file:
file_content = file.read()

# Replace the old string with the new string
updated_content = file_content.replace(old_string, new_string)

# Write the updated content back to the file
with open(file_path, "w") as file:
file.write(updated_content)
10 changes: 6 additions & 4 deletions tests/trestlebot/tasks/authored/test_compdef.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from tests import testutils
from trestlebot.const import RULES_VIEW_DIR, YAML_EXTENSION
from trestlebot.tasks.authored.base_authored import AuthoredObjectException
from trestlebot.tasks.authored.compdef import AuthoredComponentsDefinition
from trestlebot.tasks.authored.compdef import AuthoredComponentDefinition
from trestlebot.transformers.yaml_transformer import ToRulesYAMLTransformer


Expand All @@ -37,7 +37,7 @@ def test_create_new_default(tmp_trestle_dir: str) -> None:
# Prepare the workspace
trestle_root = pathlib.Path(tmp_trestle_dir)
_ = testutils.setup_for_profile(trestle_root, test_prof, "")
authored_comp = AuthoredComponentsDefinition(tmp_trestle_dir)
authored_comp = AuthoredComponentDefinition(tmp_trestle_dir)

authored_comp.create_new_default(test_prof, test_comp, "test", "My desc", "service")

Expand Down Expand Up @@ -75,7 +75,9 @@ def test_create_new_default(tmp_trestle_dir: str) -> None:
"NIST Special Publication 800-53 Revision 5 MODERATE IMPACT \
BASELINE"
)
assert rule.profile.href == "profiles/simplified_nist_profile/profile.json"
assert (
rule.profile.href == "trestle://profiles/simplified_nist_profile/profile.json"
)
assert rule.profile.include_controls is not None
assert len(rule.profile.include_controls) == 1
assert rule.profile.include_controls[0].id == "ac-5"
Expand All @@ -87,7 +89,7 @@ def test_create_new_default_no_profile(tmp_trestle_dir: str) -> None:
trestle_root = pathlib.Path(tmp_trestle_dir)
_ = testutils.setup_for_compdef(trestle_root, test_comp, "")

authored_comp = AuthoredComponentsDefinition(tmp_trestle_dir)
authored_comp = AuthoredComponentDefinition(tmp_trestle_dir)

with pytest.raises(
AuthoredObjectException, match="Profile fake does not exist in the workspace"
Expand Down
4 changes: 2 additions & 2 deletions tests/trestlebot/tasks/authored/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
AuthorObjectBase,
)
from trestlebot.tasks.authored.catalog import AuthoredCatalog
from trestlebot.tasks.authored.compdef import AuthoredComponentsDefinition
from trestlebot.tasks.authored.compdef import AuthoredComponentDefinition
from trestlebot.tasks.authored.profile import AuthoredProfile
from trestlebot.tasks.authored.ssp import AuthoredSSP

Expand Down Expand Up @@ -69,7 +69,7 @@ def test_get_authored_compdef(tmp_trestle_dir: str) -> None:
)

assert authored_object.get_trestle_root() == tmp_trestle_dir
assert isinstance(authored_object, AuthoredComponentsDefinition)
assert isinstance(authored_object, AuthoredComponentDefinition)


def test_get_authored_ssp(tmp_trestle_dir: str) -> None:
Expand Down
52 changes: 52 additions & 0 deletions tests/trestlebot/tasks/test_assemble_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@
from unittest.mock import Mock, patch

import pytest
from trestle.common.model_utils import ModelUtils
from trestle.core.commands.author.catalog import CatalogGenerate
from trestle.core.commands.author.component import ComponentGenerate
from trestle.core.commands.author.profile import ProfileGenerate
from trestle.core.commands.author.ssp import SSPGenerate
from trestle.core.models.file_content_type import FileContentType
from trestle.oscal import catalog as oscal_cat
from trestle.oscal import component as oscal_comp
from trestle.oscal import profile as oscal_prof

from tests import testutils
from trestlebot.tasks.assemble_task import AssembleTask
Expand Down Expand Up @@ -114,6 +119,12 @@ def test_catalog_assemble_task(tmp_trestle_dir: str) -> None:
cat_generate = CatalogGenerate()
assert cat_generate._run(args) == 0

# Get original last modified time
cat, _ = ModelUtils.load_model_for_class(
trestle_root, test_cat, oscal_cat.Catalog, FileContentType.JSON
)
orig_time = cat.metadata.last_modified

assemble_task = AssembleTask(
tmp_trestle_dir,
AuthoredType.CATALOG.value,
Expand All @@ -122,6 +133,12 @@ def test_catalog_assemble_task(tmp_trestle_dir: str) -> None:
)
assert assemble_task.execute() == 0

# Get new last modified time and verify catalog was modified
cat, _ = ModelUtils.load_model_for_class(
trestle_root, test_cat, oscal_cat.Catalog, FileContentType.JSON
)
assert orig_time != cat.metadata.last_modified


def test_profile_assemble_task(tmp_trestle_dir: str) -> None:
"""Test profile assemble at the task level"""
Expand All @@ -130,6 +147,13 @@ def test_profile_assemble_task(tmp_trestle_dir: str) -> None:
args = testutils.setup_for_profile(trestle_root, test_prof, md_path)
profile_generate = ProfileGenerate()
assert profile_generate._run(args) == 0

# Get original last modified time
prof, _ = ModelUtils.load_model_for_class(
trestle_root, test_prof, oscal_prof.Profile, FileContentType.JSON
)
orig_time = prof.metadata.last_modified

assemble_task = AssembleTask(
tmp_trestle_dir,
AuthoredType.PROFILE.value,
Expand All @@ -138,6 +162,12 @@ def test_profile_assemble_task(tmp_trestle_dir: str) -> None:
)
assert assemble_task.execute() == 0

# Get new last modified time adn verify profile was modified
prof, _ = ModelUtils.load_model_for_class(
trestle_root, test_prof, oscal_prof.Profile, FileContentType.JSON
)
assert orig_time != prof.metadata.last_modified


def test_compdef_assemble_task(tmp_trestle_dir: str) -> None:
"""Test compdef assemble at the task level"""
Expand All @@ -146,6 +176,18 @@ def test_compdef_assemble_task(tmp_trestle_dir: str) -> None:
args = testutils.setup_for_compdef(trestle_root, test_comp, md_path)
comp_generate = ComponentGenerate()
assert comp_generate._run(args) == 0

# Get ac-1 markdown file
comp_path = os.path.join(trestle_root, compdef_md_dir, test_comp, test_comp)
ac1_md_path = os.path.join(comp_path, test_prof, "ac", "ac-1.md")
testutils.replace_string_in_file(ac1_md_path, "partial", "implemented")

# Get original last modified time
comp, _ = ModelUtils.load_model_for_class(
trestle_root, test_comp, oscal_comp.ComponentDefinition, FileContentType.JSON
)
orig_time = comp.metadata.last_modified

assemble_task = AssembleTask(
tmp_trestle_dir,
AuthoredType.COMPDEF.value,
Expand All @@ -154,6 +196,12 @@ def test_compdef_assemble_task(tmp_trestle_dir: str) -> None:
)
assert assemble_task.execute() == 0

# Get new last modified time and verify component was modified
comp, _ = ModelUtils.load_model_for_class(
trestle_root, test_comp, oscal_comp.ComponentDefinition, FileContentType.JSON
)
assert orig_time != comp.metadata.last_modified


def test_ssp_assemble_task(tmp_trestle_dir: str) -> None:
"""Test ssp assemble at the task level"""
Expand All @@ -174,6 +222,10 @@ def test_ssp_assemble_task(tmp_trestle_dir: str) -> None:
)
assert assemble_task.execute() == 0

assert os.path.exists(
os.path.join(tmp_trestle_dir, "system-security-plans", test_ssp_output)
)


def test_ssp_assemble_task_no_index_path(tmp_trestle_dir: str) -> None:
"""Test ssp assemble at the task level with failure"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ def test_profile_regenerate_task(tmp_trestle_dir: str) -> None:
def test_compdef_regenerate_task(tmp_trestle_dir: str) -> None:
"""Test compdef regenerate at the task level"""
trestle_root = pathlib.Path(tmp_trestle_dir)

md_path = os.path.join(compdef_md_dir, test_comp)
_ = testutils.setup_for_compdef(trestle_root, test_comp, md_path)

Expand All @@ -161,7 +162,9 @@ def test_compdef_regenerate_task(tmp_trestle_dir: str) -> None:
"",
)
assert regenerate_task.execute() == 0
assert os.path.exists(os.path.join(tmp_trestle_dir, md_path))

# The compdef is a special case where each component has a separate markdown directory
assert os.path.exists(os.path.join(tmp_trestle_dir, md_path, test_comp))


def test_ssp_regenerate_task(tmp_trestle_dir: str) -> None:
Expand Down
10 changes: 6 additions & 4 deletions trestlebot/tasks/authored/compdef.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import pathlib
from typing import List

import trestle.common.const as const
import trestle.oscal.profile as prof
from trestle.common.err import TrestleError
from trestle.common.model_utils import ModelUtils
Expand All @@ -41,7 +42,7 @@
from trestlebot.transformers.yaml_transformer import FromRulesYAMLTransformer


class AuthoredComponentsDefinition(AuthorObjectBase):
class AuthoredComponentDefinition(AuthorObjectBase):
"""
Class for authoring OSCAL Component Definitions in automation
"""
Expand All @@ -62,7 +63,7 @@ def assemble(self, markdown_path: str, version_tag: str = "") -> None:
success = authoring.assemble_component_definition_markdown(
name=compdef,
output=compdef,
markdown_dir=os.path.join(markdown_path, compdef),
markdown_dir=markdown_path,
regenerate=False,
version=version_tag,
)
Expand All @@ -83,7 +84,7 @@ def regenerate(self, model_path: str, markdown_path: str) -> None:
try:
success = authoring.generate_component_definition_markdown(
name=comp_name,
output=markdown_path,
output=os.path.join(markdown_path, comp_name),
force_overwrite=False,
)
if not success:
Expand Down Expand Up @@ -165,7 +166,8 @@ def add_rules_for_profile(
name=f"rule-{control_id}",
description=f"Rule for {control_id}",
profile=Profile(
href=str(profile_path.relative_to(self._trestle_root)),
href=const.TRESTLE_HREF_HEADING
+ str(profile_path.relative_to(self._trestle_root)),
description=catalog.metadata.title,
include_controls=[Control(id=control_id)],
),
Expand Down
Loading
Loading