Skip to content

Commit

Permalink
test: adds integration test bewtween rules transform, assemble, and r… (
Browse files Browse the repository at this point in the history
#61)

* test: adds integration test bewtween rules transform, assemble, and regenerate

Fixes bug in component definition autosync
Update AuthoredComponentsDefinition to AuthoredComponentDefinition

Signed-off-by: Jennifer Power <[email protected]>

* test: improves assemble task test by adding additional assertions

Signed-off-by: Jennifer Power <[email protected]>

---------

Signed-off-by: Jennifer Power <[email protected]>
  • Loading branch information
jpower432 authored Oct 17, 2023
1 parent e8bebe2 commit 4e2fc7b
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 13 deletions.
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

0 comments on commit 4e2fc7b

Please sign in to comment.