Repository: oscal-compass/compliance-trestle (python)

Issue:
task csv-to-oscal-cd should allow any case for headings.
## Issue description / feature objectives

Different heading case should be tolerated. For example $$Control_Id_List, $$control_id_list, $$Control_id_list, etc. should all be allowed.

## Caveats / Assumptions

N/A

## Completion Criteria

Code to support and corresponding tests delivered.


Pull request:
feat: modify task csv_to_oscal_cd to allow any case for heading in csv file

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [ ] Hot fix (emergency fix and release)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Documentation (change which affects the documentation site)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Release (`develop` -> `main`)

## Quality assurance (all should be covered).

- [x] My code follows the code style of this project.
- [x] Documentation for my change is up to date?
- [x] My PR meets testing requirements.
- [x] All new and existing tests passed.
- [x] All commits are signed-off.

## Summary
Resolved #1461.
Converted all words in the heading to title cases when undecorating the heading. 
## Key links:

- [Sonar coverage](https://sonarcloud.io/dashboard?id=compliance-trestle)

# Before you merge

- Ensure it is a 'squash commit' if not a release.
- Ensure CI is currently passing
- Check sonar. If you are working for a fork a maintainer will reach out, if required.


Patch:
diff --git a/trestle/tasks/csv_to_oscal_cd.py b/trestle/tasks/csv_to_oscal_cd.py
index 0ca9d7e6..72447359 100644
--- a/trestle/tasks/csv_to_oscal_cd.py
+++ b/trestle/tasks/csv_to_oscal_cd.py
@@ -1647,11 +1647,12 @@ class _CsvMgr():
         return rval
 
     def _undecorate_header(self) -> None:
-        """Undecorate header."""
+        """Undecorate header, and reformat each word in header to title case."""
         head_row = self._csv[0]
         self._csv[0] = []
         for column_name in head_row:
             heading = self._get_normalized_column_name(column_name)
+            heading = heading.title()
             self._csv[0].append(heading)
 
     def _verify(self) -> None:


Test patch:
diff --git a/tests/trestle/tasks/csv_to_oscal_cd_test.py b/tests/trestle/tasks/csv_to_oscal_cd_test.py
index 37286f5b..392b4a53 100644
--- a/tests/trestle/tasks/csv_to_oscal_cd_test.py
+++ b/tests/trestle/tasks/csv_to_oscal_cd_test.py
@@ -382,6 +382,24 @@ def test_execute_missing_heading(tmp_path: pathlib.Path) -> None:
         assert retval == TaskOutcome.FAILURE
 
 
+def test_execute_case_insensitive_heading(tmp_path: pathlib.Path) -> None:
+    """Test execute case insensitive heading."""
+    _, section = _get_config_section_init(tmp_path, 'test-csv-to-oscal-cd.config')
+    # inject headings with different case
+    rows = _get_rows('tests/data/csv/ocp4-user.v2.csv')
+    row = rows[0]
+    assert row[2] == 'Rule_Description'
+    assert row[9] == 'Parameter_Id'
+    row[2] = 'rUlE_dEsCrIpTiOn'
+    row[9] = 'pArAmEtEr_Id'
+    with mock.patch('trestle.tasks.csv_to_oscal_cd.csv.reader') as mock_csv_reader:
+        mock_csv_reader.return_value = rows
+        tgt = csv_to_oscal_cd.CsvToOscalComponentDefinition(section)
+        retval = tgt.execute()
+        assert retval == TaskOutcome.SUCCESS
+        _validate_ocp(tmp_path)
+
+
 def test_execute_missing_value(tmp_path: pathlib.Path) -> None:
     """Test execute missing value."""
     _, section = _get_config_section_init(tmp_path, 'test-csv-to-oscal-cd.config')