From 99e2215097e2a2d7a66c51a08226234bb242bc14 Mon Sep 17 00:00:00 2001 From: Kavya Seth Date: Thu, 12 Mar 2026 23:51:34 +0530 Subject: [PATCH 1/3] Fix: remove hardcoded version in card route loader and use DeckService.getLatestVersion --- .../edition/[edition]/[card]/+page.server.ts | 86 +++++++++++-------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/cornucopia.owasp.org/src/routes/edition/[edition]/[card]/+page.server.ts b/cornucopia.owasp.org/src/routes/edition/[edition]/[card]/+page.server.ts index 24a39c913..8b81b0aa6 100644 --- a/cornucopia.owasp.org/src/routes/edition/[edition]/[card]/+page.server.ts +++ b/cornucopia.owasp.org/src/routes/edition/[edition]/[card]/+page.server.ts @@ -6,40 +6,52 @@ import { MappingService } from "$lib/services/mappingService"; import { CapecService } from "$lib/services/capecService"; export const load = (({ params }) => { - const edition = params?.edition; - const version = edition == 'webapp' ? '2.2' : edition == 'mobileapp' ? '1.1' : '1.0'; - let asvsVersion: string = "4.0.3"; - if (params.version === '3.0') asvsVersion = '5.0'; - if (!DeckService.hasEdition(edition)) error( - 404, 'Edition not found. Only: ' + DeckService.getLatestEditions().join(', ') + ' are supported.'); - - // Load CAPEC data for webapp v3.0+ - let capecData = undefined; - if (edition === 'webapp' && parseFloat(version) >= 3.0) { - capecData = CapecService.getCapecData(edition, version); - } - - return { - edition: edition, - version: version, - versions: DeckService.getVersions(edition), - lang: 'en', - card: legacyCardCodeFix(params.card.toUpperCase()), - cards: new DeckService().getCardDataForEditionVersionLang(edition, version, 'en'), - routes: new Map([ - ['ASVSRoutes', FileSystemHelper.ASVSRouteMap(asvsVersion)] - ]), - mappingData: (new MappingService()).getCardMappingForLatestEdtions(), - languages: DeckService.getLanguages(edition), - capecData - }; - - // Some QR code errors where done on the first printed decks. This will compensate for that. - function legacyCardCodeFix(card: string) { - return card.replace('COM', 'CM') - .replace('CO', 'C') - .replace('DVE', 'VE') - .replace('AC', 'AT'); - } - -}) satisfies PageServerLoad; + + const edition = params.edition; + + if (!DeckService.hasEdition(edition)) { + error( + 404, + 'Edition not found. Only: ' + + DeckService.getLatestEditions().join(', ') + + ' are supported.' + ); + } + + const version = DeckService.getLatestVersion(edition); + + let asvsVersion: string = "4.0.3"; + if (params.version === '3.0') { + asvsVersion = '5.0'; + } + + let capecData; + + if (edition === 'webapp' && parseFloat(version) >= 3.0) { + capecData = CapecService.getCapecData(edition, version); + } + + return { + edition, + version, + versions: DeckService.getVersions(edition), + lang: 'en', + card: legacyCardCodeFix(params.card.toUpperCase()), + cards: new DeckService().getCardDataForEditionVersionLang(edition, version, 'en'), + routes: new Map([ + ['ASVSRoutes', FileSystemHelper.ASVSRouteMap(asvsVersion)] + ]), + mappingData: new MappingService().getCardMappingForLatestEdtions(), + languages: DeckService.getLanguages(edition), + capecData + }; + + function legacyCardCodeFix(card: string) { + return card + .replace('COM', 'CM') + .replace('CO', 'C') + .replace('DVE', 'VE') + .replace('AC', 'AT'); + } + +}) satisfies PageServerLoad; \ No newline at end of file From a641cc706238b4b23111fbd367ffcafe93d0cea1 Mon Sep 17 00:00:00 2001 From: Kavya Seth Date: Fri, 13 Mar 2026 09:32:00 +0530 Subject: [PATCH 2/3] fix: apply black formatting --- scripts/capec_map_enricher.py | 26 +- scripts/check_translations.py | 24 +- scripts/check_translations_itest.py | 21 +- scripts/check_translations_utest.py | 109 ++- scripts/convert.py | 310 ++++++-- scripts/convert_asvs.py | 85 ++- scripts/convert_capec.py | 74 +- scripts/convert_capec_map_to_asvs_map.py | 68 +- tests/scripts/capec_map_enricher_fuzzer.py | 17 +- tests/scripts/capec_map_enricher_itest.py | 36 +- tests/scripts/capec_map_enricher_utest.py | 41 +- tests/scripts/convert_asvs_itest.py | 73 +- tests/scripts/convert_asvs_utest.py | 45 +- tests/scripts/convert_capec_itest.py | 84 ++- .../convert_capec_map_to_asvs_map_itest.py | 87 ++- .../convert_capec_map_to_asvs_map_utest.py | 141 +++- tests/scripts/convert_capec_utest.py | 112 ++- tests/scripts/convert_fuzzer.py | 135 +++- tests/scripts/convert_itest.py | 17 +- tests/scripts/convert_utest.py | 664 ++++++++++++++---- tests/scripts/smoke_tests.py | 20 +- tests/test_files/gen_mappings.py | 35 +- 22 files changed, 1782 insertions(+), 442 deletions(-) diff --git a/scripts/capec_map_enricher.py b/scripts/capec_map_enricher.py index 2a0c222c4..0c073eee7 100644 --- a/scripts/capec_map_enricher.py +++ b/scripts/capec_map_enricher.py @@ -20,7 +20,9 @@ class EnricherVars: TEMPLATE_FILE_NAME: str = "EDITION-capec-VERSION.yaml" - DEFAULT_CAPEC_JSON_PATH = Path(__file__).parent / "../cornucopia.owasp.org/data/capec-3.9/3000.json" + DEFAULT_CAPEC_JSON_PATH = ( + Path(__file__).parent / "../cornucopia.owasp.org/data/capec-3.9/3000.json" + ) DEFAULT_SOURCE_DIR = Path(__file__).parent / "../source" args: argparse.Namespace @@ -73,7 +75,9 @@ def extract_capec_names(json_data: dict[str, Any]) -> dict[int, str]: return capec_names -def enrich_capec_mappings(capec_mappings: dict[str, Any], capec_names: dict[int, str]) -> dict[str, Any]: +def enrich_capec_mappings( + capec_mappings: dict[str, Any], capec_names: dict[int, str] +) -> dict[str, Any]: """ Enrich CAPEC mappings with names from the CAPEC catalog. @@ -107,7 +111,9 @@ def enrich_capec_mappings(capec_mappings: dict[str, Any], capec_names: dict[int, enriched[capec_id_key] = enriched_entry - logging.info("Enriched %d CAPEC mappings", len(enriched) - (1 if "meta" in enriched else 0)) + logging.info( + "Enriched %d CAPEC mappings", len(enriched) - (1 if "meta" in enriched else 0) + ) return enriched @@ -155,7 +161,9 @@ def save_yaml_file(filepath: Path, data: dict[str, Any]) -> bool: """Save data as YAML file.""" try: with open(filepath, "w", encoding="utf-8") as f: - yaml.dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True) + yaml.dump( + data, f, default_flow_style=False, sort_keys=False, allow_unicode=True + ) logging.info("Successfully saved YAML file: %s", filepath) return True except Exception as e: @@ -177,7 +185,9 @@ def set_logging() -> None: def parse_arguments(input_args: list[str]) -> argparse.Namespace: """Parse command line arguments.""" - parser = argparse.ArgumentParser(description="Enrich CAPEC mappings with names from CAPEC JSON catalog") + parser = argparse.ArgumentParser( + description="Enrich CAPEC mappings with names from CAPEC JSON catalog" + ) parser.add_argument( "-c", "--capec-json", @@ -249,9 +259,9 @@ def main() -> None: if enricher_vars.args.input_path: input_path = Path(enricher_vars.args.input_path).resolve() else: - filename = EnricherVars.TEMPLATE_FILE_NAME.replace("EDITION", enricher_vars.args.edition).replace( - "VERSION", enricher_vars.args.version - ) + filename = EnricherVars.TEMPLATE_FILE_NAME.replace( + "EDITION", enricher_vars.args.edition + ).replace("VERSION", enricher_vars.args.version) input_path = directory / filename # Determine output file path diff --git a/scripts/check_translations.py b/scripts/check_translations.py index 6094de1cb..489d8d804 100644 --- a/scripts/check_translations.py +++ b/scripts/check_translations.py @@ -69,7 +69,9 @@ def get_file_groups(self) -> Dict[str, List[Path]]: return file_groups - def _separate_english_and_translations(self, files: List[Path]) -> Tuple[Path | None, List[Path]]: + def _separate_english_and_translations( + self, files: List[Path] + ) -> Tuple[Path | None, List[Path]]: """Separate English reference file from translation files.""" english_file = None translation_files = [] @@ -83,7 +85,9 @@ def _separate_english_and_translations(self, files: List[Path]) -> Tuple[Path | return english_file, translation_files - def _check_translation_tags(self, english_tags: Dict[str, str], trans_tags: Dict[str, str]) -> Dict[str, Any]: + def _check_translation_tags( + self, english_tags: Dict[str, str], trans_tags: Dict[str, str] + ) -> Dict[str, Any]: """Check translation tags against English reference.""" missing = [] untranslated = [] @@ -122,10 +126,14 @@ def check_translations(self) -> Dict[str, Dict[str, Any]]: file_groups = self.get_file_groups() for base_name, files in file_groups.items(): - english_file, translation_files = self._separate_english_and_translations(files) + english_file, translation_files = self._separate_english_and_translations( + files + ) if not english_file: - print(f"Warning: No English file found for {base_name}", file=sys.stderr) + print( + f"Warning: No English file found for {base_name}", file=sys.stderr + ) continue english_tags = self.extract_tags(english_file) @@ -153,7 +161,9 @@ def generate_markdown_report(self) -> str: return "\n".join(report_lines) report_lines.append("# Translation Check Report\n") - report_lines.append("The following sentences/tags have issues in the translations:\n") + report_lines.append( + "The following sentences/tags have issues in the translations:\n" + ) # Language name mapping lang_names = { @@ -189,7 +199,9 @@ def generate_markdown_report(self) -> str: if issues["untranslated"]: report_lines.append("### Untranslated Tags\n") - report_lines.append("The following tags have identical text to English (not translated):\n") + report_lines.append( + "The following tags have identical text to English (not translated):\n" + ) tags_str = ", ".join(issues["untranslated"]) report_lines.append(f"{tags_str}\n") diff --git a/scripts/check_translations_itest.py b/scripts/check_translations_itest.py index d23d83297..72dbadd75 100644 --- a/scripts/check_translations_itest.py +++ b/scripts/check_translations_itest.py @@ -29,12 +29,16 @@ def setUp(self) -> None: def test_source_directory_exists(self) -> None: """Test that the source directory exists.""" - self.assertTrue(self.source_dir.exists(), f"Source directory not found: {self.source_dir}") + self.assertTrue( + self.source_dir.exists(), f"Source directory not found: {self.source_dir}" + ) def test_english_files_exist(self) -> None: """Test that English card files exist.""" english_files = list(self.source_dir.glob("*-cards-*-en.yaml")) - self.assertGreater(len(english_files), 0, "No English card files found in source directory") + self.assertGreater( + len(english_files), 0, "No English card files found in source directory" + ) def test_translations_completeness(self) -> None: """ @@ -59,7 +63,9 @@ def test_translations_completeness(self) -> None: total_issues += len(issues.get("untranslated", [])) total_issues += len(issues.get("empty", [])) - self.fail(f"\\n\\nTranslation issues found ({total_issues} total):\\n\\n{report}\\n") + self.fail( + f"\\n\\nTranslation issues found ({total_issues} total):\\n\\n{report}\\n" + ) def test_no_duplicate_tags_in_english(self) -> None: """Test that English files don't have duplicate T0xxx tags.""" @@ -82,7 +88,11 @@ def test_no_duplicate_tags_in_english(self) -> None: duplicates.append(tag_id) seen_ids.add(tag_id) - self.assertEqual(len(duplicates), 0, f"Duplicate tags found in {eng_file.name}: {duplicates}") + self.assertEqual( + len(duplicates), + 0, + f"Duplicate tags found in {eng_file.name}: {duplicates}", + ) def test_tag_format(self) -> None: """Test that tags follow the T0xxxx format.""" @@ -95,7 +105,8 @@ def test_tag_format(self) -> None: for tag_id in tags.keys(): self.assertIsNotNone( - tag_pattern.match(tag_id), f"Tag {tag_id} in {eng_file.name} doesn't match format T0xxxx" + tag_pattern.match(tag_id), + f"Tag {tag_id} in {eng_file.name} doesn't match format T0xxxx", ) def test_generate_markdown_report(self) -> None: diff --git a/scripts/check_translations_utest.py b/scripts/check_translations_utest.py index da73061ab..3271abe91 100644 --- a/scripts/check_translations_utest.py +++ b/scripts/check_translations_utest.py @@ -86,7 +86,9 @@ def test_tag_format_validation(self) -> None: tags = self.checker.extract_tags(english_file) for tag_id in tags.keys(): - self.assertIsNotNone(tag_pattern.match(tag_id), f"Tag {tag_id} doesn't match format T0xxxx") + self.assertIsNotNone( + tag_pattern.match(tag_id), f"Tag {tag_id} doesn't match format T0xxxx" + ) def test_no_duplicate_tags(self) -> None: """Test that files don't have duplicate T0xxx tags.""" @@ -131,7 +133,11 @@ def test_whitespace_only_detected_as_empty(self) -> None: result = self.checker._check_translation_tags(english_tags, trans_tags) - self.assertIn("T00002", result["empty"], "Whitespace-only translation should be detected as empty") + self.assertIn( + "T00002", + result["empty"], + "Whitespace-only translation should be detected as empty", + ) def test_tabs_and_newlines_detected_as_empty(self) -> None: english_tags = {"T00001": "Submit", "T00002": "Cancel"} @@ -139,7 +145,9 @@ def test_tabs_and_newlines_detected_as_empty(self) -> None: result = self.checker._check_translation_tags(english_tags, trans_tags) - self.assertIn("T00002", result["empty"], "Tabs and newlines should be detected as empty") + self.assertIn( + "T00002", result["empty"], "Tabs and newlines should be detected as empty" + ) def test_extra_spaces_detected_as_untranslated(self) -> None: english_tags = {"T00001": "Login", "T00002": "Register"} @@ -147,16 +155,25 @@ def test_extra_spaces_detected_as_untranslated(self) -> None: result = self.checker._check_translation_tags(english_tags, trans_tags) - self.assertIn("T00001", result["untranslated"], "Extra spaces should be detected as untranslated") + self.assertIn( + "T00001", + result["untranslated"], + "Extra spaces should be detected as untranslated", + ) def test_properly_translated_no_issues(self) -> None: english_tags = {"T00001": "Login", "T00002": "Register"} - trans_tags = {"T00001": "Iniciar Sesión", "T00002": "Registrar"} # Proper translations + trans_tags = { + "T00001": "Iniciar Sesión", + "T00002": "Registrar", + } # Proper translations result = self.checker._check_translation_tags(english_tags, trans_tags) self.assertEqual(len(result["missing"]), 0, "No missing tags expected") - self.assertEqual(len(result["untranslated"]), 0, "No untranslated tags expected") + self.assertEqual( + len(result["untranslated"]), 0, "No untranslated tags expected" + ) self.assertEqual(len(result["empty"]), 0, "No empty tags expected") @@ -176,9 +193,21 @@ def test_compound_locale_files_included_in_file_groups(self) -> None: self.assertIn("test-cards-1.0", file_groups) files = [f.name for f in file_groups["test-cards-1.0"]] - self.assertIn("test-cards-1.0-no_nb.yaml", files, "Norwegian (no_nb) file should be included") - self.assertIn("test-cards-1.0-pt_br.yaml", files, "Portuguese Brazil (pt_br) file should be included") - self.assertIn("test-cards-1.0-pt_pt.yaml", files, "Portuguese Portugal (pt_pt) file should be included") + self.assertIn( + "test-cards-1.0-no_nb.yaml", + files, + "Norwegian (no_nb) file should be included", + ) + self.assertIn( + "test-cards-1.0-pt_br.yaml", + files, + "Portuguese Brazil (pt_br) file should be included", + ) + self.assertIn( + "test-cards-1.0-pt_pt.yaml", + files, + "Portuguese Portugal (pt_pt) file should be included", + ) def test_compound_locale_not_excluded_by_old_len_filter(self) -> None: """Regression test: ensure 5-char locale codes are NOT filtered out.""" @@ -203,34 +232,58 @@ def test_norwegian_translation_issues_detected(self) -> None: results = self.checker.check_translations() self.assertIn("test-cards-1.0", results) - self.assertIn("no_nb", results["test-cards-1.0"], "Norwegian (no_nb) results should be present") + self.assertIn( + "no_nb", + results["test-cards-1.0"], + "Norwegian (no_nb) results should be present", + ) no_nb_issues = results["test-cards-1.0"]["no_nb"] - self.assertIn("T00004", no_nb_issues["missing"], "T00004 should be missing in no_nb") - self.assertIn("T00003", no_nb_issues["empty"], "T00003 should be empty in no_nb") + self.assertIn( + "T00004", no_nb_issues["missing"], "T00004 should be missing in no_nb" + ) + self.assertIn( + "T00003", no_nb_issues["empty"], "T00003 should be empty in no_nb" + ) def test_portuguese_brazil_untranslated_detected(self) -> None: """Test that untranslated tags in Portuguese Brazil (pt_br) are detected.""" results = self.checker.check_translations() self.assertIn("test-cards-1.0", results) - self.assertIn("pt_br", results["test-cards-1.0"], "Portuguese Brazil (pt_br) results should be present") + self.assertIn( + "pt_br", + results["test-cards-1.0"], + "Portuguese Brazil (pt_br) results should be present", + ) pt_br_issues = results["test-cards-1.0"]["pt_br"] - self.assertIn("T00002", pt_br_issues["untranslated"], "T00002 should be untranslated in pt_br") + self.assertIn( + "T00002", + pt_br_issues["untranslated"], + "T00002 should be untranslated in pt_br", + ) def test_portuguese_portugal_fully_translated_no_issues(self) -> None: """Test that a fully-translated Portuguese Portugal (pt_pt) file produces no issues.""" results = self.checker.check_translations() pt_pt_issues = results.get("test-cards-1.0", {}).get("pt_pt", None) - self.assertIsNone(pt_pt_issues, "pt_pt has no issues and should not appear in results") + self.assertIsNone( + pt_pt_issues, "pt_pt has no issues and should not appear in results" + ) def test_lang_names_in_report(self) -> None: """Test that generate_markdown_report resolves compound locale display names correctly.""" self.checker.check_translations() report = self.checker.generate_markdown_report() - self.assertIn("Norwegian", report, "Norwegian display name should appear for no_nb") - self.assertIn("Portuguese (Brazil)", report, "Portuguese (Brazil) display name should appear for pt_br") + self.assertIn( + "Norwegian", report, "Norwegian display name should appear for no_nb" + ) + self.assertIn( + "Portuguese (Brazil)", + report, + "Portuguese (Brazil) display name should appear for pt_br", + ) class TestCoverageBranches(unittest.TestCase): @@ -256,13 +309,17 @@ def test_get_file_groups_skips_archive_files(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: tmp_path = Path(tmpdir) # Place a YAML file whose name contains "archive" directly in source dir - (tmp_path / "webapp-archive-cards-1.0-en.yaml").write_text("---\nparagraphs: []") + (tmp_path / "webapp-archive-cards-1.0-en.yaml").write_text( + "---\nparagraphs: []" + ) (tmp_path / "test-cards-1.0-en.yaml").write_text("---\nparagraphs: []") checker = TranslationChecker(tmp_path) file_groups = checker.get_file_groups() all_files = [f.name for files in file_groups.values() for f in files] self.assertNotIn( - "webapp-archive-cards-1.0-en.yaml", all_files, "Files with 'archive' in name should be skipped" + "webapp-archive-cards-1.0-en.yaml", + all_files, + "Files with 'archive' in name should be skipped", ) def test_check_translations_no_english_file(self) -> None: @@ -293,7 +350,9 @@ def test_check_translations_empty_english_tags(self) -> None: (tmp_path / "test-cards-1.0-es.yaml").write_text("---\nparagraphs: []") checker = TranslationChecker(tmp_path) results = checker.check_translations() - self.assertEqual(results, {}, "Should produce no results when English has no tags") + self.assertEqual( + results, {}, "Should produce no results when English has no tags" + ) def test_main_exits_when_source_dir_missing(self) -> None: """Test that main() exits with code 1 when source directory does not exist.""" @@ -314,9 +373,13 @@ def test_main_runs_and_writes_report(self) -> None: mock_checker.check_translations.return_value = {} mock_checker.generate_markdown_report.return_value = "# Report\n\u2705 Done" - with patch.object(check_translations, "TranslationChecker", return_value=mock_checker), patch( - "builtins.open", mock_open() - ), patch("builtins.print"), self.assertRaises(SystemExit) as cm: + with patch.object( + check_translations, "TranslationChecker", return_value=mock_checker + ), patch("builtins.open", mock_open()), patch( + "builtins.print" + ), self.assertRaises( + SystemExit + ) as cm: check_translations.main() self.assertEqual(cm.exception.code, 0) diff --git a/scripts/convert.py b/scripts/convert.py index 3f60754fc..17088ec12 100644 --- a/scripts/convert.py +++ b/scripts/convert.py @@ -24,7 +24,19 @@ class ConvertVars: EDITION_CHOICES: List[str] = ["all", "webapp", "mobileapp", "against-security"] FILETYPE_CHOICES: List[str] = ["all", "docx", "odt", "pdf", "idml"] LAYOUT_CHOICES: List[str] = ["all", "leaflet", "guide", "cards"] - LANGUAGE_CHOICES: List[str] = ["all", "en", "es", "fr", "nl", "no-nb", "pt-pt", "pt-br", "hu", "it", "ru"] + LANGUAGE_CHOICES: List[str] = [ + "all", + "en", + "es", + "fr", + "nl", + "no-nb", + "pt-pt", + "pt-br", + "hu", + "it", + "ru", + ] VERSION_CHOICES: List[str] = ["all", "latest", "1.0", "1.1", "2.2", "3.0", "5.0"] LATEST_VERSION_CHOICES: List[str] = ["1.1", "3.0"] TEMPLATE_CHOICES: List[str] = ["all", "bridge", "bridge_qr", "tarot", "tarot_qr"] @@ -35,9 +47,15 @@ class ConvertVars: "all": {"2.2": "2.2", "1.0": "1.0", "1.1": "1.1", "3.0": "3.0", "5.0": "5.0"}, } DEFAULT_TEMPLATE_FILENAME: str = os.sep.join( - ["resources", "templates", "owasp_cornucopia_edition_ver_layout_document_template_lang"] + [ + "resources", + "templates", + "owasp_cornucopia_edition_ver_layout_document_template_lang", + ] + ) + DEFAULT_OUTPUT_FILENAME: str = os.sep.join( + ["output", "owasp_cornucopia_edition_ver_layout_document_template_lang"] ) - DEFAULT_OUTPUT_FILENAME: str = os.sep.join(["output", "owasp_cornucopia_edition_ver_layout_document_template_lang"]) args: argparse.Namespace can_convert_to_pdf: bool = False @@ -64,7 +82,9 @@ def check_make_list_into_text(var: List[str]) -> str: return text_output -def _validate_file_paths(source_filename: str, output_pdf_filename: str) -> Tuple[bool, str, str]: +def _validate_file_paths( + source_filename: str, output_pdf_filename: str +) -> Tuple[bool, str, str]: """Validate and sanitize file paths to prevent command injection.""" source_path = os.path.abspath(source_filename) output_dir = os.path.abspath(os.path.dirname(output_pdf_filename)) @@ -106,14 +126,33 @@ def _safe_extractall(archive: zipfile.ZipFile, target_dir: str) -> None: # Block any member whose resolved path escapes the target directory. # The os.sep suffix prevents prefix collisions (e.g. /tmp/d vs /tmp/d_evil). if not member_path.startswith(abs_target + os.sep): - raise ValueError(f"Zip Slip blocked: member '{member.filename}' would extract outside target directory") + raise ValueError( + f"Zip Slip blocked: member '{member.filename}' would extract outside target directory" + ) archive.extract(member, target_dir) def _validate_command_args(cmd_args: List[str]) -> bool: """Validate command arguments for dangerous characters.""" - dangerous_chars = ["&", "|", ";", "$", "`", "(", ")", "<", ">", "*", "?", "[", "]", "{", "}", "\\"] + dangerous_chars = [ + "&", + "|", + ";", + "$", + "`", + "(", + ")", + "<", + ">", + "*", + "?", + "[", + "]", + "{", + "}", + "\\", + ] for arg in cmd_args: if any(char in arg for char in dangerous_chars): logging.warning(f"Potentially dangerous character found in argument: {arg}") @@ -135,13 +174,17 @@ def _convert_with_libreoffice(source_filename: str, output_pdf_filename: str) -> logging.info(f"Using LibreOffice for conversion: {libreoffice_bin}") # Validate file paths - is_valid, source_path, output_dir = _validate_file_paths(source_filename, output_pdf_filename) + is_valid, source_path, output_dir = _validate_file_paths( + source_filename, output_pdf_filename + ) if not is_valid: logging.warning(source_path) # source_path contains the error message return False # Create user profile directory safely - user_profile_dir = os.path.abspath(os.path.join(convert_vars.BASE_PATH, "output", "lo_profile")) + user_profile_dir = os.path.abspath( + os.path.join(convert_vars.BASE_PATH, "output", "lo_profile") + ) os.makedirs(user_profile_dir, exist_ok=True) user_profile_url = "file:///" + user_profile_dir.replace("\\", "/") @@ -163,7 +206,11 @@ def _convert_with_libreoffice(source_filename: str, output_pdf_filename: str) -> # Execute conversion subprocess.run( - cmd_args, check=True, capture_output=True, text=True, timeout=300 # 5 minute timeout to prevent hanging + cmd_args, + check=True, + capture_output=True, + text=True, + timeout=300, # 5 minute timeout to prevent hanging ) return True except subprocess.TimeoutExpired: @@ -246,15 +293,23 @@ def convert_to_pdf(source_filename: str, output_pdf_filename: str) -> None: def create_edition_from_template( - layout: str, language: str = "en", template: str = "bridge", version: str = "3.0", edition: str = "webapp" + layout: str, + language: str = "en", + template: str = "bridge", + version: str = "3.0", + edition: str = "webapp", ) -> None: # Get the list of available translation files - yaml_files = get_files_from_of_type(os.sep.join([convert_vars.BASE_PATH, "source"]), "yaml") + yaml_files = get_files_from_of_type( + os.sep.join([convert_vars.BASE_PATH, "source"]), "yaml" + ) if not yaml_files: return - mapping: Dict[str, Any] = get_mapping_for_edition(yaml_files, version, language, edition, template, layout) + mapping: Dict[str, Any] = get_mapping_for_edition( + yaml_files, version, language, edition, template, layout + ) if not mapping: logging.warning( @@ -264,7 +319,9 @@ def create_edition_from_template( # return # Get the language data from the correct language file (checks vars.args.language to select the correct file) - language_data: Dict[str, Dict[str, str]] = get_language_data(yaml_files, language, version, edition) + language_data: Dict[str, Dict[str, str]] = get_language_data( + yaml_files, language, version, edition + ) # Transform the language data into the template mapping language_dict: Dict[str, str] = map_language_data_to_template(language_data) @@ -308,7 +365,14 @@ def create_edition_from_template( logging.info(f"New file saved: {output_file}") -def valid_meta(meta: Dict[str, Any], language: str, edition: str, version: str, template: str, layout: str) -> bool: +def valid_meta( + meta: Dict[str, Any], + language: str, + edition: str, + version: str, + template: str, + layout: str, +) -> bool: if not has_translation_for_edition(meta, language): logging.warning( f"Translation in {language} does not exist for edition: {edition}, version: {version} " @@ -377,7 +441,9 @@ def main() -> None: for language in get_valid_language_choices(): for template in get_valid_templates(): for version in get_valid_version_choices(): - create_edition_from_template(layout, language, template, version, edition) + create_edition_from_template( + layout, language, template, version, edition + ) def parse_arguments(input_args: List[str]) -> argparse.Namespace: @@ -389,7 +455,9 @@ def parse_arguments(input_args: List[str]) -> argparse.Namespace: description += "\nExample usage: c:\\cornucopia\\scripts\\convert.py -t bridge -lt cards -l fr -v 2.2 -o" description += " my_output_folder\\owasp_cornucopia_edition_version_layout_language_template.idml" parser = argparse.ArgumentParser( - description=description, formatter_class=argparse.RawTextHelpFormatter, exit_on_error=False + description=description, + formatter_class=argparse.RawTextHelpFormatter, + exit_on_error=False, ) parser.add_argument( "-i", @@ -569,15 +637,22 @@ def get_files_from_of_type(path: str, ext: str) -> List[str]: for filename in fnmatch.filter(filenames, "*." + str(ext)): files.append(os.path.join(root, filename)) if not files: - logging.error("No language files found in folder: %s", str(os.sep.join([convert_vars.BASE_PATH, "source"]))) + logging.error( + "No language files found in folder: %s", + str(os.sep.join([convert_vars.BASE_PATH, "source"])), + ) return files logging.debug( - "%s%s", f" --- found {len(files)} files of type {ext}. Showing first few:\n* ", str("\n* ".join(files[:3])) + "%s%s", + f" --- found {len(files)} files of type {ext}. Showing first few:\n* ", + str("\n* ".join(files[:3])), ) return files -def get_find_replace_list(meta: Dict[str, str], template: str, layout: str) -> List[Tuple[str, str]]: +def get_find_replace_list( + meta: Dict[str, str], template: str, layout: str +) -> List[Tuple[str, str]]: ll: List[Tuple[str, str]] = [ ("_edition", "_" + meta["edition"].lower()), ("_layout", "_" + layout.lower()), @@ -597,13 +672,22 @@ def get_full_tag(cat_id: str, id: str, tag: str) -> str: def get_mapping_for_edition( - yaml_files: List[str], version: str, language: str, edition: str, template: str, layout: str + yaml_files: List[str], + version: str, + language: str, + edition: str, + template: str, + layout: str, ) -> Dict[str, Any]: - mapping_data: Dict[str, Any] = get_mapping_data_for_edition(yaml_files, language, version, edition) + mapping_data: Dict[str, Any] = get_mapping_data_for_edition( + yaml_files, language, version, edition + ) if not mapping_data: logging.warning("No mapping file found") return {} - if "meta" not in mapping_data or not valid_meta(mapping_data["meta"], language, edition, version, template, layout): + if "meta" not in mapping_data or not valid_meta( + mapping_data["meta"], language, edition, version, template, layout + ): logging.warning("Metadata is missing or invalid in mapping file") return {} try: @@ -639,13 +723,22 @@ def get_mapping_data_for_edition( except yaml.YAMLError as e: logging.info(f"Error loading yaml file: {mappingfile}. Error = {e}") data = {} - if "meta" in data.keys() and "component" in data["meta"].keys() and data["meta"]["component"] == "mappings": + if ( + "meta" in data.keys() + and "component" in data["meta"].keys() + and data["meta"]["component"] == "mappings" + ): logging.debug(" --- found mappings file: " + os.path.split(mappingfile)[1]) else: - logging.debug(" --- found source file, but it was missing metadata: " + os.path.split(mappingfile)[1]) + logging.debug( + " --- found source file, but it was missing metadata: " + + os.path.split(mappingfile)[1] + ) if "meta" in list(data.keys()): meta_keys = data["meta"].keys() - logging.debug(f" --- data.keys() = {data.keys()}, data[meta].keys() = {meta_keys}") + logging.debug( + f" --- data.keys() = {data.keys()}, data[meta].keys() = {meta_keys}" + ) data = {} logging.debug(f" --- Len = {len(data)}.") return data @@ -663,8 +756,12 @@ def build_template_dict(input_data: Dict[str, Any]) -> Dict[str, Any]: text_type = "sentences" logging.debug(f" --- key = {key}.") logging.debug(f" --- suit name = {paragraphs['name']}") - logging.debug(f" --- suit id = {is_valid_string_argument(paragraphs['id'])}") - full_tag = "${{{}}}".format("_".join([is_valid_string_argument(paragraphs["id"]), "suit"])) + logging.debug( + f" --- suit id = {is_valid_string_argument(paragraphs['id'])}" + ) + full_tag = "${{{}}}".format( + "_".join([is_valid_string_argument(paragraphs["id"]), "suit"]) + ) logging.debug(f" --- suit tag = {full_tag}") if data["meta"]["component"] == "cards": data[full_tag] = paragraphs["name"] @@ -674,7 +771,9 @@ def build_template_dict(input_data: Dict[str, Any]) -> Dict[str, Any]: logging.debug(f" --- text = {text_output}") logging.debug(f" --- paragraph = {paragraph}") full_tag = get_full_tag( - is_valid_string_argument(paragraphs["id"]), is_valid_string_argument(paragraph["id"]), tag + is_valid_string_argument(paragraphs["id"]), + is_valid_string_argument(paragraph["id"]), + tag, ) logging.debug(f" --- full tag = {full_tag}") data[full_tag] = check_make_list_into_text(text_output) @@ -692,7 +791,15 @@ def get_meta_data(data: Dict[str, Any]) -> Dict[str, Any]: logging.error("Meta tag is not a dictionary.") return meta - valid_keys = ("edition", "component", "language", "version", "languages", "layouts", "templates") + valid_keys = ( + "edition", + "component", + "language", + "version", + "languages", + "layouts", + "templates", + ) for key in valid_keys: if key in raw_meta: value = raw_meta[key] @@ -727,10 +834,14 @@ def get_language_data( """Get the raw data of the replacement text from correct yaml file""" language_file: str = "" for file in yaml_files: - if is_yaml_file(file) and is_lang_file_for_version(file, version, language, edition): + if is_yaml_file(file) and is_lang_file_for_version( + file, version, language, edition + ): language_file = file if not language_file: - logging.error(f"Did not find translation for version: {version}, lang: {language}, edition: {edition}") + logging.error( + f"Did not find translation for version: {version}, lang: {language}, edition: {edition}" + ) return {} logging.debug(f" --- Loading language file: {language_file}") @@ -759,7 +870,10 @@ def is_mapping_file_for_version(path: str, version: str, edition: str) -> bool: def is_lang_file_for_version(path: str, version: str, lang: str, edition: str) -> bool: filename = os.path.basename(path).lower() # Support both -en. and -en_US. style - lang_patterns = ["-" + lang.lower() + ".", "-" + lang.lower().replace("-", "_") + "."] + lang_patterns = [ + "-" + lang.lower() + ".", + "-" + lang.lower().replace("-", "_") + ".", + ] has_lang = any(filename.find(p) != -1 for p in lang_patterns) return ( @@ -778,15 +892,21 @@ def map_language_data_to_template(input_data: Dict[str, Any]) -> Dict[str, str]: try: data = build_template_dict(input_data) except Exception as e: - logging.warning(f"Could not build valid template mapping. The Yaml file is not valid. Got exception: {e}") + logging.warning( + f"Could not build valid template mapping. The Yaml file is not valid. Got exception: {e}" + ) data = input_data if convert_vars.args.debug: debug_txt = " --- Translation data showing First 4 (key: text):\n* " - debug_txt += "\n* ".join(l1 + ": " + str(data[l1]) for l1 in list(data.keys())[:4]) + debug_txt += "\n* ".join( + l1 + ": " + str(data[l1]) for l1 in list(data.keys())[:4] + ) logging.debug(debug_txt) debug_txt = " --- Translation data showing Last 4 (key: text):\n* " - debug_txt += "\n* ".join(l1 + ": " + str(data[l1]) for l1 in list(data.keys())[-4:]) + debug_txt += "\n* ".join( + l1 + ": " + str(data[l1]) for l1 in list(data.keys())[-4:] + ) logging.debug(debug_txt) return data @@ -805,7 +925,9 @@ def get_replacement_mapping_value(k: str, v: str, el_text: str) -> str: return "" -def get_replacement_value_from_dict(el_text: str, replacement_values: List[Tuple[str, str]]) -> str: +def get_replacement_value_from_dict( + el_text: str, replacement_values: List[Tuple[str, str]] +) -> str: # Fast path: if no $ and no OWASP, likely no tags if "$" not in el_text and "OWASP" not in el_text: return el_text @@ -839,7 +961,9 @@ def get_suit_tags_and_key(key: str, edition: str) -> Tuple[List[str], str]: return suit_tags, suit_key -def get_template_for_edition(layout: str = "guide", template: str = "bridge", edition: str = "webapp") -> str: +def get_template_for_edition( + layout: str = "guide", template: str = "bridge", edition: str = "webapp" +) -> str: template_doc: str args_input_file: str = convert_vars.args.inputfile sfile_ext = "idml" @@ -850,22 +974,38 @@ def get_template_for_edition(layout: str = "guide", template: str = "bridge", ed if os.path.isabs(args_input_file): template_doc = args_input_file elif os.path.isfile(convert_vars.BASE_PATH + os.sep + args_input_file): - template_doc = os.path.normpath(convert_vars.BASE_PATH + os.sep + args_input_file) - elif os.path.isfile(convert_vars.BASE_PATH + os.sep + args_input_file.replace(".." + os.sep, "")): template_doc = os.path.normpath( - convert_vars.BASE_PATH + os.sep + args_input_file.replace(".." + os.sep, "") + convert_vars.BASE_PATH + os.sep + args_input_file + ) + elif os.path.isfile( + convert_vars.BASE_PATH + os.sep + args_input_file.replace(".." + os.sep, "") + ): + template_doc = os.path.normpath( + convert_vars.BASE_PATH + + os.sep + + args_input_file.replace(".." + os.sep, "") ) elif args_input_file.find("..") == -1 and os.path.isfile( convert_vars.BASE_PATH + os.sep + ".." + os.sep + args_input_file ): - template_doc = os.path.normpath(convert_vars.BASE_PATH + os.sep + ".." + os.sep + args_input_file) - elif os.path.isfile(convert_vars.BASE_PATH + os.sep + args_input_file.replace("scripts" + os.sep, "")): template_doc = os.path.normpath( - convert_vars.BASE_PATH + os.sep + args_input_file.replace("scripts" + os.sep, "") + convert_vars.BASE_PATH + os.sep + ".." + os.sep + args_input_file + ) + elif os.path.isfile( + convert_vars.BASE_PATH + + os.sep + + args_input_file.replace("scripts" + os.sep, "") + ): + template_doc = os.path.normpath( + convert_vars.BASE_PATH + + os.sep + + args_input_file.replace("scripts" + os.sep, "") ) else: template_doc = args_input_file - logging.debug(f" --- Template_doc NOT found. Input File = {args_input_file}") + logging.debug( + f" --- Template_doc NOT found. Input File = {args_input_file}" + ) else: # No input file specified - using defaults template_doc = os.path.normpath( @@ -885,7 +1025,9 @@ def get_template_for_edition(layout: str = "guide", template: str = "bridge", ed logging.debug(f" --- Returning template_doc = {template_doc}") return template_doc else: - logging.error(f"Source file not found: {template_doc}. Please ensure file exists and try again.") + logging.error( + f"Source file not found: {template_doc}. Please ensure file exists and try again." + ) return "None" @@ -920,7 +1062,10 @@ def get_valid_version_choices() -> List[str]: edition: str = convert_vars.args.edition.lower() if convert_vars.args.version.lower() == "all": for version in convert_vars.VERSION_CHOICES: - if version not in ("all", "latest") and not get_valid_mapping_for_version(version, edition) == "": + if ( + version not in ("all", "latest") + and not get_valid_mapping_for_version(version, edition) == "" + ): versions.append(version) elif convert_vars.args.version == "" or convert_vars.args.version == "latest": for version in convert_vars.LATEST_VERSION_CHOICES: @@ -930,7 +1075,9 @@ def get_valid_version_choices() -> List[str]: versions.append(convert_vars.args.version) if not versions: - logging.debug(f"No deck with version: {convert_vars.args.version} for edition: {edition} exists") + logging.debug( + f"No deck with version: {convert_vars.args.version} for edition: {edition} exists" + ) return versions @@ -953,7 +1100,10 @@ def get_valid_templates() -> List[str]: def get_valid_edition_choices() -> List[str]: editions = [] - if convert_vars.args.edition.lower() == "all" or not convert_vars.args.edition.lower(): + if ( + convert_vars.args.edition.lower() == "all" + or not convert_vars.args.edition.lower() + ): for edition in convert_vars.EDITION_CHOICES: if edition not in "all": editions.append(edition) @@ -982,13 +1132,17 @@ def save_docx_file(doc: Any, output_file: str) -> None: doc.save(output_file) -def save_odt_file(template_doc: str, language_dict: Dict[str, str], output_file: str) -> None: +def save_odt_file( + template_doc: str, language_dict: Dict[str, str], output_file: str +) -> None: # Get the output path and temp output path to put the temp xml files output_path = os.path.join(convert_vars.BASE_PATH, "output") temp_output_path = os.path.join(output_path, "temp_odt") # Ensure the output folder and temp output folder exist ensure_folder_exists(temp_output_path) - logging.debug(" --- temp_folder for extraction of xml files = %s", str(temp_output_path)) + logging.debug( + " --- temp_folder for extraction of xml files = %s", str(temp_output_path) + ) # Unzip source xml files and place in temp output folder with zipfile.ZipFile(template_doc) as odt_archive: @@ -1004,7 +1158,9 @@ def save_odt_file(template_doc: str, language_dict: Dict[str, str], output_file: replace_text_in_xml_file(xml_file, replacement_values) # Zip the files as an odt file in output folder - logging.debug(" --- finished replacing text in xml files. Now zipping into odt file") + logging.debug( + " --- finished replacing text in xml files. Now zipping into odt file" + ) zip_dir(temp_output_path, output_file) # If not debugging, delete temp folder and files @@ -1012,18 +1168,25 @@ def save_odt_file(template_doc: str, language_dict: Dict[str, str], output_file: shutil.rmtree(temp_output_path, ignore_errors=True) -def save_idml_file(template_doc: str, language_dict: Dict[str, str], output_file: str) -> None: +def save_idml_file( + template_doc: str, language_dict: Dict[str, str], output_file: str +) -> None: # Get the output path and temp output path to put the temp xml files output_path = convert_vars.BASE_PATH + os.sep + "output" temp_output_path = output_path + os.sep + "temp" # Ensure the output folder and temp output folder exist ensure_folder_exists(temp_output_path) - logging.debug(" --- temp_folder for extraction of xml files = %s", str(temp_output_path)) + logging.debug( + " --- temp_folder for extraction of xml files = %s", str(temp_output_path) + ) # Unzip source xml files and place in temp output folder with zipfile.ZipFile(template_doc) as idml_archive: _safe_extractall(idml_archive, temp_output_path) - logging.debug(" --- namelist of first few files in archive = %s", str(idml_archive.namelist()[:5])) + logging.debug( + " --- namelist of first few files in archive = %s", + str(idml_archive.namelist()[:5]), + ) xml_files = get_files_from_of_type(temp_output_path, "xml") replacement_values = sort_keys_longest_to_shortest(language_dict) @@ -1034,7 +1197,9 @@ def save_idml_file(template_doc: str, language_dict: Dict[str, str], output_file replace_text_in_xml_file(file, replacement_values) # Zip the files as an idml file in output folder - logging.debug(" --- finished replacing text in xml files. Now zipping into idml file") + logging.debug( + " --- finished replacing text in xml files. Now zipping into idml file" + ) zip_dir(temp_output_path, output_file) # If not debugging, delete temp folder and files @@ -1044,9 +1209,13 @@ def save_idml_file(template_doc: str, language_dict: Dict[str, str], output_file def set_can_convert_to_pdf() -> bool: operating_system: str = sys.platform.lower() - can_convert = operating_system.find("win") != -1 or operating_system.find("darwin") != -1 + can_convert = ( + operating_system.find("win") != -1 or operating_system.find("darwin") != -1 + ) convert_vars.can_convert_to_pdf = can_convert - logging.debug(f" --- operating system = {operating_system}, can_convert_to_pdf = {convert_vars.can_convert_to_pdf}") + logging.debug( + f" --- operating system = {operating_system}, can_convert_to_pdf = {convert_vars.can_convert_to_pdf}" + ) return can_convert @@ -1060,12 +1229,16 @@ def set_logging() -> None: logging.getLogger().setLevel(logging.INFO) -def sort_keys_longest_to_shortest(replacement_dict: Dict[str, str]) -> List[Tuple[str, str]]: +def sort_keys_longest_to_shortest( + replacement_dict: Dict[str, str], +) -> List[Tuple[str, str]]: new_list = list((k, v) for k, v in replacement_dict.items()) return sorted(new_list, key=lambda s: len(s[0]), reverse=True) -def remove_short_keys(replacement_dict: Dict[str, str], min_length: int = 8) -> Dict[str, str]: +def remove_short_keys( + replacement_dict: Dict[str, str], min_length: int = 8 +) -> Dict[str, str]: data2: Dict[str, str] = {} for key, value in replacement_dict.items(): if len(key) >= min_length: @@ -1077,7 +1250,9 @@ def remove_short_keys(replacement_dict: Dict[str, str], min_length: int = 8) -> return data2 -def rename_output_file(file_extension: str, template: str, layout: str, meta: Dict[str, str]) -> str: +def rename_output_file( + file_extension: str, template: str, layout: str, meta: Dict[str, str] +) -> str: """Rename output file replacing place-holders from meta dict (edition, component, language, version).""" args_output_file: str = convert_vars.args.outputfile logging.debug(f" --- args_output_file = {args_output_file}") @@ -1086,12 +1261,19 @@ def rename_output_file(file_extension: str, template: str, layout: str, meta: Di if os.path.isabs(args_output_file): output_filename = args_output_file else: - output_filename = str(Path(convert_vars.BASE_PATH + os.sep + args_output_file)) + output_filename = str( + Path(convert_vars.BASE_PATH + os.sep + args_output_file) + ) else: # No output file specified - using default output_filename = str( - Path(convert_vars.BASE_PATH + os.sep + convert_vars.DEFAULT_OUTPUT_FILENAME + file_extension) + Path( + convert_vars.BASE_PATH + + os.sep + + convert_vars.DEFAULT_OUTPUT_FILENAME + + file_extension + ) ) logging.debug(f" --- output_filename before fix extension = {output_filename}") @@ -1151,7 +1333,9 @@ def _find_xml_elements(tree: Any) -> List[ElTree.Element]: return cast(List[ElTree.Element], elements) -def replace_text_in_xml_file(filename: str, replacement_values: List[Tuple[str, str]]) -> None: +def replace_text_in_xml_file( + filename: str, replacement_values: List[Tuple[str, str]] +) -> None: logging.debug(f" --- starting xml_replace for {filename}") try: tree = DefusedElTree.parse(filename) diff --git a/scripts/convert_asvs.py b/scripts/convert_asvs.py index eff5868fd..111479bbc 100644 --- a/scripts/convert_asvs.py +++ b/scripts/convert_asvs.py @@ -15,12 +15,16 @@ class ConvertVars: - DEFAULT_OUTPUT_PATH = Path(Path(__file__).parent / "../cornucopia.owasp.org/data/taxonomy/en/ASVS-5.0") + DEFAULT_OUTPUT_PATH = Path( + Path(__file__).parent / "../cornucopia.owasp.org/data/taxonomy/en/ASVS-5.0" + ) DEFAULT_INPUT_PATH = ( Path(__file__).parent / "../cornucopia.owasp.org/data/asvs-5.0/en/" "OWASP_Application_Security_Verification_Standard_5.0.0_en.json" ) - DEFAULT_ASVS_TO_CAPEC_INPUT_PATH = Path(__file__).parent / "../source/webapp-asvs-3.0.yaml" + DEFAULT_ASVS_TO_CAPEC_INPUT_PATH = ( + Path(__file__).parent / "../source/webapp-asvs-3.0.yaml" + ) LATEST_CAPEC_VERSION_CHOICES: List[str] = ["3.9"] LATEST_ASVS_VERSION_CHOICES: List[str] = ["5.0"] args: argparse.Namespace @@ -30,7 +34,11 @@ def create_level_summary(level: int, arr: List[dict[str, Any]]) -> None: topic = "" category = "" os.mkdir(Path(convert_vars.args.output_path, f"level-{level}-controls")) - f = open(Path(convert_vars.args.output_path, f"level-{level}-controls/index.md"), "w", encoding="utf-8") + f = open( + Path(convert_vars.args.output_path, f"level-{level}-controls/index.md"), + "w", + encoding="utf-8", + ) f.write(f"# Level {level} controls\n\n") f.write(f"Level {level} contains {len(arr)} controls listed below: \n\n") for link in arr: @@ -40,14 +48,21 @@ def create_level_summary(level: int, arr: List[dict[str, Any]]) -> None: if link["cat"] != category: category = link["cat"] f.write(f"### {category}\n\n") - shortdesc = link["description"].replace("Verify that", "").strip().capitalize()[0:50] + " ..." + shortdesc = ( + link["description"].replace("Verify that", "").strip().capitalize()[0:50] + + " ..." + ) f.write(f"- [{link['name']}]({link['link']}) *{shortdesc}* \n\n") f.close() def _format_directory_name(ordinal: int, name: str) -> str: """Format ordinal and name into directory-safe string.""" - return str(ordinal).rjust(2, "0") + "-" + name.lower().replace(" ", "-").replace(",", "") + return ( + str(ordinal).rjust(2, "0") + + "-" + + name.lower().replace(" ", "-").replace(",", "") + ) def _get_level_requirement_text(level: int) -> str: @@ -112,11 +127,17 @@ def _categorize_by_level( def _write_subitem_content( - file_handle: TextIO, subitem: dict[str, Any], asvs_map: dict[str, dict[str, List[str]]], capec_version: str + file_handle: TextIO, + subitem: dict[str, Any], + asvs_map: dict[str, dict[str, List[str]]], + capec_version: str, ) -> None: """Write a single subitem's content to the file.""" file_handle.write("## " + subitem["Shortcode"] + "\n\n") - file_handle.write(subitem["Description"].encode("ascii", "ignore").decode("utf8", "ignore") + "\n\n") + file_handle.write( + subitem["Description"].encode("ascii", "ignore").decode("utf8", "ignore") + + "\n\n" + ) level = int(subitem["L"]) level_text = _get_level_requirement_text(level) @@ -128,7 +149,9 @@ def _write_subitem_content( logging.info("ASVS ID %s has no CAPEC mapping", asvs_id) else: file_handle.write("### Related CAPEC™ Requirements\n\n") - file_handle.write(f"CAPEC™ ({capec_version}): {create_link_list(asvs_map.get(asvs_id, {}), capec_version)}\n\n") + file_handle.write( + f"CAPEC™ ({capec_version}): {create_link_list(asvs_map.get(asvs_id, {}), capec_version)}\n\n" + ) logging.debug("object: %s", subitem) logging.debug("🟪") @@ -157,13 +180,18 @@ def _process_requirement_item( for subitem in item["Items"]: _write_subitem_content(f, subitem, asvs_map, capec_version) - _categorize_by_level(subitem, requirement_name, item_name, L1, L2, L3, asvs_version) + _categorize_by_level( + subitem, requirement_name, item_name, L1, L2, L3, asvs_version + ) _write_disclaimer(f) def create_asvs_pages( - data: dict[str, Any], asvs_map: dict[str, dict[str, List[str]]], capec_version: str, asvs_version: str + data: dict[str, Any], + asvs_map: dict[str, dict[str, List[str]]], + capec_version: str, + asvs_version: str, ) -> None: """Create ASVS documentation pages from JSON data.""" L1: List[dict[str, Any]] = [] @@ -171,29 +199,44 @@ def create_asvs_pages( L3: List[dict[str, Any]] = [] for requirement in data["Requirements"]: - requirement_name = _format_directory_name(requirement["Ordinal"], requirement["Name"]) + requirement_name = _format_directory_name( + requirement["Ordinal"], requirement["Name"] + ) logging.debug(requirement_name) os.mkdir(Path(convert_vars.args.output_path, requirement_name)) for item in requirement["Items"]: - _process_requirement_item(item, requirement_name, asvs_map, capec_version, L1, L2, L3, asvs_version) + _process_requirement_item( + item, + requirement_name, + asvs_map, + capec_version, + L1, + L2, + L3, + asvs_version, + ) create_level_summary(1, L1) create_level_summary(2, L2) create_level_summary(3, L3) -def has_no_capec_mapping(asvs_id: str, capec_to_asvs_map: dict[str, dict[str, List[str]]]) -> bool: - return not capec_to_asvs_map.get(asvs_id) or not capec_to_asvs_map.get(asvs_id, {"capec_codes": []}).get( - "capec_codes" - ) +def has_no_capec_mapping( + asvs_id: str, capec_to_asvs_map: dict[str, dict[str, List[str]]] +) -> bool: + return not capec_to_asvs_map.get(asvs_id) or not capec_to_asvs_map.get( + asvs_id, {"capec_codes": []} + ).get("capec_codes") def create_link_list(requirements: dict[str, Any], capec_version: str) -> str: link_list = "" asvs_requirements = requirements.get("capec_codes", []) if not asvs_requirements: - logging.debug("No CAPEC requirements found in requirements: %s", str(requirements)) + logging.debug( + "No CAPEC requirements found in requirements: %s", str(requirements) + ) return "" sorted_requirements = sorted(asvs_requirements) for idx, capec_code in enumerate(sorted_requirements): @@ -205,7 +248,9 @@ def create_link_list(requirements: dict[str, Any], capec_version: str) -> str: def parse_arguments(input_args: list[str]) -> argparse.Namespace: - parser = argparse.ArgumentParser(description="Convert CAPEC™ JSON to Cornucopia format") + parser = argparse.ArgumentParser( + description="Convert CAPEC™ JSON to Cornucopia format" + ) parser.add_argument( "-o", "--output-path", @@ -327,7 +372,9 @@ def main() -> None: empty_folder(Path(convert_vars.args.output_path)) create_folder(Path(convert_vars.args.output_path)) data = load_json_file(Path(convert_vars.args.input_path)) - asvs_map: dict[str, dict[str, List[str]]] = load_asvs_to_capec_mapping(Path(convert_vars.args.asvs_to_capec)) + asvs_map: dict[str, dict[str, List[str]]] = load_asvs_to_capec_mapping( + Path(convert_vars.args.asvs_to_capec) + ) create_asvs_pages(data, asvs_map, capec_version, asvs_version) logging.info("ASVS conversion process completed") diff --git a/scripts/convert_capec.py b/scripts/convert_capec.py index 92d4717e3..4b7126376 100644 --- a/scripts/convert_capec.py +++ b/scripts/convert_capec.py @@ -15,9 +15,15 @@ class ConvertVars: - DEFAULT_OUTPUT_PATH = Path(__file__).parent / "../cornucopia.owasp.org/data/taxonomy/en/capec-3.9" - DEFAULT_INPUT_PATH = Path(__file__).parent / "../cornucopia.owasp.org/data/capec-3.9/3000.json" - DEFAULT_CAPEC_TO_ASVS_INPUT_PATH = Path(__file__).parent / "../source/webapp-capec-3.0.yaml" + DEFAULT_OUTPUT_PATH = ( + Path(__file__).parent / "../cornucopia.owasp.org/data/taxonomy/en/capec-3.9" + ) + DEFAULT_INPUT_PATH = ( + Path(__file__).parent / "../cornucopia.owasp.org/data/capec-3.9/3000.json" + ) + DEFAULT_CAPEC_TO_ASVS_INPUT_PATH = ( + Path(__file__).parent / "../source/webapp-capec-3.0.yaml" + ) DEFAULT_ASVS_MAPPING_PATH = ( Path(__file__).parent / "../cornucopia.owasp.org/data/asvs-5.0/en/" "OWASP_Application_Security_Verification_Standard_5.0.0_en.json" @@ -45,7 +51,9 @@ def create_capec_pages( f.write("## Description\n\n") f.write(f"{parse_description(i.get('Description', ''))}\n\n") capec_id = int(i["_ID"]) - f.write(f"Source: [CAPEC™ {capec_id}](https://capec.mitre.org/data/definitions/{capec_id}.html)\n\n") + f.write( + f"Source: [CAPEC™ {capec_id}](https://capec.mitre.org/data/definitions/{capec_id}.html)\n\n" + ) if has_no_asvs_mapping(capec_id, capec_to_asvs_map): logging.debug("CAPEC ID %d has no ASVS mapping", capec_id) else: @@ -67,7 +75,9 @@ def create_capec_pages( f.write("## Description\n\n") f.write(f"{parse_description(i.get('Summary', ''))}\n\n") capec_id = int(i["_ID"]) - f.write(f"Source: [CAPEC™ {capec_id}](https://capec.mitre.org/data/definitions/{capec_id}.html)\n\n") + f.write( + f"Source: [CAPEC™ {capec_id}](https://capec.mitre.org/data/definitions/{capec_id}.html)\n\n" + ) if has_no_asvs_mapping(capec_id, capec_to_asvs_map): logging.debug("CAPEC ID %d has no ASVS mapping", capec_id) else: @@ -80,31 +90,47 @@ def create_capec_pages( logging.info("Created %d CAPEC pages", pages) -def has_no_asvs_mapping(capec_id: int, capec_to_asvs_map: dict[int, dict[str, List[str]]]) -> bool: - return not capec_to_asvs_map.get(capec_id) or not capec_to_asvs_map.get(capec_id, {"owasp_asvs": []}).get( - "owasp_asvs" - ) +def has_no_asvs_mapping( + capec_id: int, capec_to_asvs_map: dict[int, dict[str, List[str]]] +) -> bool: + return not capec_to_asvs_map.get(capec_id) or not capec_to_asvs_map.get( + capec_id, {"owasp_asvs": []} + ).get("owasp_asvs") def parse_description(description_field: Any) -> str: """Parse CAPEC description field which can be dict, list, or string.""" if isinstance(description_field, dict): - if "Description" in description_field and "p" in description_field["Description"]: + if ( + "Description" in description_field + and "p" in description_field["Description"] + ): p_content = description_field["Description"]["p"] if isinstance(p_content, dict) and "__text" in p_content: return str(p_content["__text"]) elif isinstance(p_content, list): return " ".join( - [str(p["__text"]) if isinstance(p, dict) and "__text" in p else str(p) for p in p_content] + [ + ( + str(p["__text"]) + if isinstance(p, dict) and "__text" in p + else str(p) + ) + for p in p_content + ] ) return str(description_field) -def create_link_list(requirements: dict[str, Any], asvs_map: dict[str, Any], asvs_version: str) -> str: +def create_link_list( + requirements: dict[str, Any], asvs_map: dict[str, Any], asvs_version: str +) -> str: link_list = "" asvs_requirements = requirements.get("owasp_asvs", []) if not asvs_requirements: - logging.debug("No ASVS requirements found in requirements: %s", str(requirements)) + logging.debug( + "No ASVS requirements found in requirements: %s", str(requirements) + ) return "" sorted_requirements = sorted(asvs_requirements) for idx, shortcode in enumerate(sorted_requirements): @@ -117,14 +143,22 @@ def create_link_list(requirements: dict[str, Any], asvs_map: dict[str, Any], asv def createlink(data: dict[str, Any], shortcode: str, asvs_version: str) -> str: for i in data["Requirements"]: - name = str(i["Ordinal"]).rjust(2, "0") + "-" + i["Name"].lower().replace(" ", "-").replace(",", "") + name = ( + str(i["Ordinal"]).rjust(2, "0") + + "-" + + i["Name"].lower().replace(" ", "-").replace(",", "") + ) for item in i["Items"]: itemname = ( - str(item["Ordinal"]).rjust(2, "0") + "-" + item["Name"].lower().replace(" ", "-").replace(",", "") + str(item["Ordinal"]).rjust(2, "0") + + "-" + + item["Name"].lower().replace(" ", "-").replace(",", "") ) for subitem in item["Items"]: - if shortcode.removeprefix("V") == subitem["Shortcode"].removeprefix("V"): + if shortcode.removeprefix("V") == subitem["Shortcode"].removeprefix( + "V" + ): return f"[{shortcode}](/taxonomy/asvs-{asvs_version}/{name}/{itemname}#{subitem['Shortcode']})" return shortcode if shortcode else "" @@ -203,7 +237,9 @@ def load_capec_to_asvs_mapping(filepath: Path) -> dict[int, dict[str, List[str]] def parse_arguments(input_args: list[str]) -> argparse.Namespace: - parser = argparse.ArgumentParser(description="Convert CAPEC JSON to Cornucopia format") + parser = argparse.ArgumentParser( + description="Convert CAPEC JSON to Cornucopia format" + ) parser.add_argument( "-o", "--output-path", @@ -280,7 +316,9 @@ def main() -> None: if not asvs_map: logging.error("Failed to load ASVS mapping data") return - capec_to_asvs_map = load_capec_to_asvs_mapping(Path(convert_vars.args.capec_to_asvs)) + capec_to_asvs_map = load_capec_to_asvs_mapping( + Path(convert_vars.args.capec_to_asvs) + ) if not capec_to_asvs_map: logging.error("Failed to load CAPEC to ASVS mapping") return diff --git a/scripts/convert_capec_map_to_asvs_map.py b/scripts/convert_capec_map_to_asvs_map.py index d85f35597..cd02ba658 100644 --- a/scripts/convert_capec_map_to_asvs_map.py +++ b/scripts/convert_capec_map_to_asvs_map.py @@ -38,11 +38,15 @@ def extract_asvs_to_capec_mappings(data: dict[str, Any]) -> dict[str, set[str]]: for suit in data["suits"]: _extract_asvs_mapping_from_suit(suit, asvs_to_capec_map) - logging.info("Extracted mappings for %d unique ASVS requirements", len(asvs_to_capec_map)) + logging.info( + "Extracted mappings for %d unique ASVS requirements", len(asvs_to_capec_map) + ) return asvs_to_capec_map -def _extract_asvs_mapping_from_suit(suit: dict[str, Any], asvs_to_capec_map: dict[str, set[str]]) -> None: +def _extract_asvs_mapping_from_suit( + suit: dict[str, Any], asvs_to_capec_map: dict[str, set[str]] +) -> None: """Process a single suit and extract ASVS mappings from its cards.""" if "cards" not in suit: return @@ -51,7 +55,9 @@ def _extract_asvs_mapping_from_suit(suit: dict[str, Any], asvs_to_capec_map: dic _extract_asvs_mapping_from_card(card, asvs_to_capec_map) -def _extract_asvs_mapping_from_card(card: dict[str, Any], asvs_to_capec_map: dict[str, set[str]]) -> None: +def _extract_asvs_mapping_from_card( + card: dict[str, Any], asvs_to_capec_map: dict[str, set[str]] +) -> None: """Process a single card and extract its ASVS mappings.""" if "capec_map" not in card: return @@ -94,7 +100,9 @@ def extract_capec_mappings(data: dict[str, Any]) -> dict[int, set[str]]: return capec_to_asvs_map -def _extract_capec_mapping_from_suit(suit: dict[str, Any], capec_to_asvs_map: dict[int, set[str]]) -> None: +def _extract_capec_mapping_from_suit( + suit: dict[str, Any], capec_to_asvs_map: dict[int, set[str]] +) -> None: """Process a single suit and extract CAPEC mappings from its cards.""" if "cards" not in suit: return @@ -103,7 +111,9 @@ def _extract_capec_mapping_from_suit(suit: dict[str, Any], capec_to_asvs_map: di _extract_capec_mapping_from_card(card, capec_to_asvs_map) -def _extract_capec_mapping_from_card(card: dict[str, Any], capec_to_asvs_map: dict[int, set[str]]) -> None: +def _extract_capec_mapping_from_card( + card: dict[str, Any], capec_to_asvs_map: dict[int, set[str]] +) -> None: """Process a single card and extract its CAPEC mappings.""" if "capec_map" not in card: return @@ -192,7 +202,9 @@ def save_yaml_file(filepath: Path, data: Dict[str, Any]) -> bool: """Save data as YAML file.""" try: with open(filepath, "w", encoding="utf-8") as f: - yaml.dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True) + yaml.dump( + data, f, default_flow_style=False, sort_keys=False, allow_unicode=True + ) logging.info("Successfully saved YAML file: %s", filepath) return True except Exception as e: @@ -213,7 +225,9 @@ def set_logging() -> None: def parse_arguments(input_args: list[str]) -> argparse.Namespace: """Parse command line arguments.""" - parser = argparse.ArgumentParser(description="Convert webapp-mappings YAML to CAPEC-to-ASVS mapping format") + parser = argparse.ArgumentParser( + description="Convert webapp-mappings YAML to CAPEC-to-ASVS mapping format" + ) parser.add_argument( "-i", "--input-path", @@ -310,16 +324,22 @@ def main() -> None: if convert_vars.args.input_path: input_path = Path(convert_vars.args.input_path).resolve() else: - input_path = directory / ConvertVars.TEMPLATE_FILE_NAME.replace("TEMPLATE", "mappings").replace( - "VERSION", str(convert_vars.args.version) - ).replace("EDITION", str(convert_vars.args.edition)) - - capec_output_path = directory / ConvertVars.TEMPLATE_FILE_NAME.replace("TEMPLATE", "capec").replace( - "VERSION", str(convert_vars.args.version) - ).replace("EDITION", str(convert_vars.args.edition)) - asvs_output_path = directory / ConvertVars.TEMPLATE_FILE_NAME.replace("TEMPLATE", "asvs").replace( - "VERSION", str(convert_vars.args.version) - ).replace("EDITION", str(convert_vars.args.edition)) + input_path = directory / ConvertVars.TEMPLATE_FILE_NAME.replace( + "TEMPLATE", "mappings" + ).replace("VERSION", str(convert_vars.args.version)).replace( + "EDITION", str(convert_vars.args.edition) + ) + + capec_output_path = directory / ConvertVars.TEMPLATE_FILE_NAME.replace( + "TEMPLATE", "capec" + ).replace("VERSION", str(convert_vars.args.version)).replace( + "EDITION", str(convert_vars.args.edition) + ) + asvs_output_path = directory / ConvertVars.TEMPLATE_FILE_NAME.replace( + "TEMPLATE", "asvs" + ).replace("VERSION", str(convert_vars.args.version)).replace( + "EDITION", str(convert_vars.args.edition) + ) logging.info("Input file: %s", input_path) logging.info("Output file: %s", capec_output_path) @@ -359,19 +379,27 @@ def main() -> None: sys.exit(1) logging.info("CAPEC-to-ASVS mapping conversion completed successfully") - logging.info("Total CAPEC codes processed: %d", len(output_data) - (1 if meta else 0)) + logging.info( + "Total CAPEC codes processed: %d", len(output_data) - (1 if meta else 0) + ) asvs_to_capec_map = extract_asvs_to_capec_mappings(data) # Pass enrichment data here output_data_asvs = convert_to_output_format( - asvs_to_capec_map, parameter="capec_codes", meta=meta, enrichment_data=asvs_details + asvs_to_capec_map, + parameter="capec_codes", + meta=meta, + enrichment_data=asvs_details, ) # Save output YAML if not save_yaml_file(asvs_output_path, output_data_asvs): logging.error("Failed to save asvs output file") sys.exit(1) logging.info("ASVS-to-CAPEC mapping conversion completed successfully") - logging.info("Total ASVS requirements processed: %d", len(output_data_asvs) - (1 if meta else 0)) + logging.info( + "Total ASVS requirements processed: %d", + len(output_data_asvs) - (1 if meta else 0), + ) if __name__ == "__main__": diff --git a/tests/scripts/capec_map_enricher_fuzzer.py b/tests/scripts/capec_map_enricher_fuzzer.py index 5bb098324..8a328def8 100644 --- a/tests/scripts/capec_map_enricher_fuzzer.py +++ b/tests/scripts/capec_map_enricher_fuzzer.py @@ -8,7 +8,9 @@ # Add the root directory to sys.path to import scripts sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))) -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "scripts"))) +sys.path.append( + os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "scripts")) +) try: import scripts.capec_map_enricher as enricher @@ -66,7 +68,10 @@ def test_main(data): for _ in range(fdp.ConsumeIntInRange(0, 5)): key = fdp.ConsumeIntInRange(1, 10000) fuzzed_yaml_data[key] = { - "owasp_asvs": [fdp.ConsumeUnicodeNoSurrogates(16) for _ in range(fdp.ConsumeIntInRange(0, 3))] + "owasp_asvs": [ + fdp.ConsumeUnicodeNoSurrogates(16) + for _ in range(fdp.ConsumeIntInRange(0, 3)) + ] } if fdp.ConsumeBool(): fuzzed_yaml_data[key]["extra_field"] = fdp.ConsumeUnicodeNoSurrogates(32) @@ -93,9 +98,13 @@ def test_main(data): try: # Mocking the file I/O to avoid hitting the disk and to feed fuzzed data # We also mock parse_arguments to avoid SystemExit from pathvalidate/argparse early on - with patch.object(enricher, "load_json_file", return_value=fuzzed_json_data), patch.object( + with patch.object( + enricher, "load_json_file", return_value=fuzzed_json_data + ), patch.object( enricher, "load_yaml_file", return_value=fuzzed_yaml_data - ), patch.object(enricher, "save_yaml_file", return_value=fdp.ConsumeBool()), patch.object( + ), patch.object( + enricher, "save_yaml_file", return_value=fdp.ConsumeBool() + ), patch.object( enricher, "parse_arguments", return_value=argparse.Namespace(**argp) ), patch( "sys.argv", ["capec_map_enricher.py"] + args diff --git a/tests/scripts/capec_map_enricher_itest.py b/tests/scripts/capec_map_enricher_itest.py index 6a8d2d597..0753eb41b 100644 --- a/tests/scripts/capec_map_enricher_itest.py +++ b/tests/scripts/capec_map_enricher_itest.py @@ -25,8 +25,12 @@ def setUp(self) -> None: self.base_path = Path(__file__).parent.parent.parent.resolve() # Set up test input file paths - self.test_capec_json = self.base_path / "tests" / "test_files" / "capec-3.9" / "3000.json" - self.test_capec_yaml = self.base_path / "tests" / "test_files" / "source" / "webapp-capec-3.0.yaml" + self.test_capec_json = ( + self.base_path / "tests" / "test_files" / "capec-3.9" / "3000.json" + ) + self.test_capec_yaml = ( + self.base_path / "tests" / "test_files" / "source" / "webapp-capec-3.0.yaml" + ) # Create temporary output directory self.temp_output_dir = tempfile.mkdtemp() @@ -34,9 +38,13 @@ def setUp(self) -> None: # Verify test input files exist if not self.test_capec_json.exists(): - raise FileNotFoundError(f"Test CAPEC JSON file not found: {self.test_capec_json}") + raise FileNotFoundError( + f"Test CAPEC JSON file not found: {self.test_capec_json}" + ) if not self.test_capec_yaml.exists(): - raise FileNotFoundError(f"Test CAPEC YAML file not found: {self.test_capec_yaml}") + raise FileNotFoundError( + f"Test CAPEC YAML file not found: {self.test_capec_yaml}" + ) def tearDown(self) -> None: """Clean up test output directory""" @@ -74,7 +82,9 @@ def test_extract_capec_names_from_test_data(self): # Verify we extracted some names self.assertIsInstance(capec_names, dict) - self.assertGreater(len(capec_names), 0, "Should extract at least one CAPEC name") + self.assertGreater( + len(capec_names), 0, "Should extract at least one CAPEC name" + ) # Verify the structure for capec_id, name in capec_names.items(): @@ -91,7 +101,9 @@ def test_specific_capec_names_in_test_data(self): expected_ids = [1, 5, 560] for capec_id in expected_ids: - self.assertIn(capec_id, capec_names, f"CAPEC-{capec_id} should be in extracted names") + self.assertIn( + capec_id, capec_names, f"CAPEC-{capec_id} should be in extracted names" + ) self.assertIsInstance(capec_names[capec_id], str) self.assertGreater(len(capec_names[capec_id]), 0) @@ -188,12 +200,20 @@ def test_save_and_reload_preserves_data(self): for capec_id in enriched.keys(): self.assertIn(capec_id, reloaded) self.assertEqual(reloaded[capec_id]["name"], enriched[capec_id]["name"]) - self.assertEqual(reloaded[capec_id]["owasp_asvs"], enriched[capec_id]["owasp_asvs"]) + self.assertEqual( + reloaded[capec_id]["owasp_asvs"], enriched[capec_id]["owasp_asvs"] + ) def test_output_to_test_files_directory(self): """Test saving output to the tests/test_files/output directory""" # Set up the expected output path - expected_output_path = self.base_path / "tests" / "test_files" / "output" / "edition-capec-latest.yaml" + expected_output_path = ( + self.base_path + / "tests" + / "test_files" + / "output" + / "edition-capec-latest.yaml" + ) # Create output directory if it doesn't exist expected_output_path.parent.mkdir(parents=True, exist_ok=True) diff --git a/tests/scripts/capec_map_enricher_utest.py b/tests/scripts/capec_map_enricher_utest.py index 748a63780..10c2acf79 100644 --- a/tests/scripts/capec_map_enricher_utest.py +++ b/tests/scripts/capec_map_enricher_utest.py @@ -12,7 +12,9 @@ class ConvertVars: OUTPUT_DIR: str = str(Path(__file__).parent.parent.resolve() / "test_files/output") - OUTPUT_FILE: str = str(Path(__file__).parent.parent.resolve() / OUTPUT_DIR / "enriched_capec.yaml") + OUTPUT_FILE: str = str( + Path(__file__).parent.parent.resolve() / OUTPUT_DIR / "enriched_capec.yaml" + ) if "unittest.util" in __import__("sys").modules: @@ -96,7 +98,11 @@ def test_extract_capec_names_missing_attack_pattern(self): def test_extract_capec_names_not_list(self): """Test with Attack_Pattern not being a list""" - data = {"Attack_Pattern_Catalog": {"Attack_Patterns": {"Attack_Pattern": "not a list"}}} + data = { + "Attack_Pattern_Catalog": { + "Attack_Patterns": {"Attack_Pattern": "not a list"} + } + } with self.assertLogs(logging.getLogger(), logging.WARNING) as log: result = enricher.extract_capec_names(data) @@ -216,7 +222,9 @@ def test_load_valid_json(self, mock_file): def test_load_file_not_found(self, mock_file): """Test loading non-existent file""" with self.assertLogs(logging.getLogger(), logging.ERROR) as log: - result = enricher.load_json_file(Path(ConvertVars.OUTPUT_DIR + "/nonexistent.json")) + result = enricher.load_json_file( + Path(ConvertVars.OUTPUT_DIR + "/nonexistent.json") + ) self.assertEqual(result, {}) self.assertIn("File not found", log.output[0]) @@ -225,7 +233,9 @@ def test_load_file_not_found(self, mock_file): def test_load_invalid_json(self, mock_file): """Test loading file with invalid JSON""" with self.assertLogs(logging.getLogger(), logging.ERROR) as log: - result = enricher.load_json_file(Path(ConvertVars.OUTPUT_DIR + "/invalid.json")) + result = enricher.load_json_file( + Path(ConvertVars.OUTPUT_DIR + "/invalid.json") + ) self.assertEqual(result, {}) self.assertIn("Error parsing JSON file", log.output[0]) @@ -234,7 +244,11 @@ def test_load_invalid_json(self, mock_file): class TestLoadYamlFile(unittest.TestCase): """Test load_yaml_file function""" - @patch("builtins.open", new_callable=mock_open, read_data="1:\n owasp_asvs:\n - 1.2.3\n") + @patch( + "builtins.open", + new_callable=mock_open, + read_data="1:\n owasp_asvs:\n - 1.2.3\n", + ) @patch("yaml.safe_load") def test_load_valid_yaml(self, mock_yaml_load, mock_file): """Test loading a valid YAML file""" @@ -252,7 +266,9 @@ def test_load_valid_yaml(self, mock_yaml_load, mock_file): def test_load_yaml_file_not_found(self, mock_file): """Test loading non-existent YAML file""" with self.assertLogs(logging.getLogger(), logging.ERROR) as log: - result = enricher.load_yaml_file(Path(ConvertVars.OUTPUT_DIR + "/nonexistent.yaml")) + result = enricher.load_yaml_file( + Path(ConvertVars.OUTPUT_DIR + "/nonexistent.yaml") + ) self.assertEqual(result, {}) self.assertIn("File not found", log.output[0]) @@ -279,7 +295,9 @@ def test_save_io_error(self, mock_file): data = {1: {"name": "Test"}} with self.assertLogs(logging.getLogger(), logging.ERROR) as log: - result = enricher.save_yaml_file(Path(ConvertVars.OUTPUT_DIR + "/error.yaml"), data) + result = enricher.save_yaml_file( + Path(ConvertVars.OUTPUT_DIR + "/error.yaml"), data + ) self.assertFalse(result) self.assertIn("Error saving YAML file", log.output[0]) @@ -379,7 +397,14 @@ class TestMainFunction(unittest.TestCase): @patch("scripts.capec_map_enricher.parse_arguments") @patch("sys.exit") def test_main_successful_execution( - self, mock_exit, mock_parse_args, mock_load_json, mock_load_yaml, mock_extract_names, mock_enrich, mock_save + self, + mock_exit, + mock_parse_args, + mock_load_json, + mock_load_yaml, + mock_extract_names, + mock_enrich, + mock_save, ): """Test successful main execution""" # Setup mocks diff --git a/tests/scripts/convert_asvs_itest.py b/tests/scripts/convert_asvs_itest.py index bbb0ba2ab..8a0b6ba59 100644 --- a/tests/scripts/convert_asvs_itest.py +++ b/tests/scripts/convert_asvs_itest.py @@ -33,7 +33,9 @@ def setUp(self) -> None: / "asvs-5.0" / "OWASP_Application_Security_Verification_Standard_5.0.0_en.json" ) - self.test_asvs_to_capec = self.base_path / "tests" / "test_files" / "source" / "webapp-asvs-3.0.yaml" + self.test_asvs_to_capec = ( + self.base_path / "tests" / "test_files" / "source" / "webapp-asvs-3.0.yaml" + ) # Create temporary output directory self.temp_output_dir = tempfile.mkdtemp() @@ -41,9 +43,13 @@ def setUp(self) -> None: # Verify test files exist if not self.test_input_file.exists(): - raise FileNotFoundError(f"Test input file not found: {self.test_input_file}") + raise FileNotFoundError( + f"Test input file not found: {self.test_input_file}" + ) if not self.test_asvs_to_capec.exists(): - raise FileNotFoundError(f"Test ASVS to CAPEC mapping file not found: {self.test_asvs_to_capec}") + raise FileNotFoundError( + f"Test ASVS to CAPEC mapping file not found: {self.test_asvs_to_capec}" + ) def tearDown(self) -> None: """Clean up test output directory""" @@ -58,7 +64,9 @@ def test_load_test_json_file(self): self.assertIsInstance(data, dict) self.assertIn("Requirements", data) self.assertIn("Name", data) - self.assertEqual(data["Name"], "Application Security Verification Standard Project") + self.assertEqual( + data["Name"], "Application Security Verification Standard Project" + ) def test_load_test_yaml_mapping(self): """Test loading the test ASVS to CAPEC YAML mapping file""" @@ -139,11 +147,21 @@ def test_create_asvs_pages_integration(self): # Check that some directories were created output_dirs = [d for d in self.test_output_path.iterdir() if d.is_dir()] - self.assertGreater(len(output_dirs), 0, "Should have created at least one directory") + self.assertGreater( + len(output_dirs), 0, "Should have created at least one directory" + ) # Check for level control directories - level_dirs = [d for d in output_dirs if d.name.startswith("level-") and d.name.endswith("-controls")] - self.assertEqual(len(level_dirs), 3, "Should have created 3 level control directories (L1, L2, L3)") + level_dirs = [ + d + for d in output_dirs + if d.name.startswith("level-") and d.name.endswith("-controls") + ] + self.assertEqual( + len(level_dirs), + 3, + "Should have created 3 level control directories (L1, L2, L3)", + ) # Verify level control index files exist for level in [1, 2, 3]: @@ -175,16 +193,22 @@ def test_create_asvs_pages_with_requirements(self): # Find a requirement directory (e.g., 01-encoding-and-sanitization) requirement_dirs = [ - d for d in self.test_output_path.iterdir() if d.is_dir() and not d.name.startswith("level-") + d + for d in self.test_output_path.iterdir() + if d.is_dir() and not d.name.startswith("level-") ] - self.assertGreater(len(requirement_dirs), 0, "Should have created requirement directories") + self.assertGreater( + len(requirement_dirs), 0, "Should have created requirement directories" + ) # Check first requirement directory first_req_dir = sorted(requirement_dirs)[0] # It should have subdirectories for items item_dirs = [d for d in first_req_dir.iterdir() if d.is_dir()] - self.assertGreater(len(item_dirs), 0, "Requirement should have item subdirectories") + self.assertGreater( + len(item_dirs), 0, "Requirement should have item subdirectories" + ) # Check first item directory for index.md first_item_dir = sorted(item_dirs)[0] @@ -219,7 +243,9 @@ def test_create_asvs_pages_with_capec_links(self): # Look for pages with CAPEC mappings # Check the first requirement requirement_dirs = [ - d for d in self.test_output_path.iterdir() if d.is_dir() and not d.name.startswith("level-") + d + for d in self.test_output_path.iterdir() + if d.is_dir() and not d.name.startswith("level-") ] found_capec_reference = False @@ -243,7 +269,9 @@ def test_create_asvs_pages_with_capec_links(self): def test_main_integration(self): """Test the main function with real test files""" # Create permanent output directory for this test - test_output_dir = self.base_path / "tests" / "test_files" / "output" / "asvs_integration_test" + test_output_dir = ( + self.base_path / "tests" / "test_files" / "output" / "asvs_integration_test" + ) # Clean up if exists if test_output_dir.exists(): @@ -265,7 +293,9 @@ def test_main_integration(self): asvs.convert_vars.args = asvs.parse_arguments(test_args) # Run main workflow (without calling main() to avoid sys.argv issues) - capec_version = asvs.get_valid_capec_version(asvs.convert_vars.args.capec_version) + capec_version = asvs.get_valid_capec_version( + asvs.convert_vars.args.capec_version + ) asvs_version = asvs.get_valid_asvs_version(asvs.convert_vars.args.asvs_version) asvs.set_logging() @@ -273,7 +303,9 @@ def test_main_integration(self): asvs.empty_folder(Path(asvs.convert_vars.args.output_path)) asvs.create_folder(Path(asvs.convert_vars.args.output_path)) data = asvs.load_json_file(Path(asvs.convert_vars.args.input_path)) - asvs_map = asvs.load_asvs_to_capec_mapping(Path(asvs.convert_vars.args.asvs_to_capec)) + asvs_map = asvs.load_asvs_to_capec_mapping( + Path(asvs.convert_vars.args.asvs_to_capec) + ) asvs.create_asvs_pages(data, asvs_map, capec_version, asvs_version) # Verify logging @@ -285,7 +317,9 @@ def test_main_integration(self): # Verify some content was created output_items = list(test_output_dir.iterdir()) - self.assertGreater(len(output_items), 0, "Should have created output files/directories") + self.assertGreater( + len(output_items), 0, "Should have created output files/directories" + ) # Clean up if test_output_dir.exists(): @@ -371,7 +405,9 @@ def test_create_and_empty_folder(self): (sub_folder / "file3.txt").write_text("content3") # Verify content exists - self.assertEqual(len(list(test_folder.rglob("*"))), 4) # 2 files + 1 folder + 1 file in folder + self.assertEqual( + len(list(test_folder.rglob("*"))), 4 + ) # 2 files + 1 folder + 1 file in folder # Empty the folder asvs.empty_folder(test_folder) @@ -383,7 +419,10 @@ def test_load_and_save_json(self): """Test loading JSON from real file""" # Create a test JSON file test_json_file = self.test_path / "test.json" - test_data = {"Requirements": [{"Ordinal": 1, "Name": "Test Requirement"}], "Version": "5.0.0"} + test_data = { + "Requirements": [{"Ordinal": 1, "Name": "Test Requirement"}], + "Version": "5.0.0", + } with open(test_json_file, "w", encoding="utf-8") as f: import json diff --git a/tests/scripts/convert_asvs_utest.py b/tests/scripts/convert_asvs_utest.py index b51f73d0e..6c3ce35f4 100644 --- a/tests/scripts/convert_asvs_utest.py +++ b/tests/scripts/convert_asvs_utest.py @@ -50,7 +50,9 @@ def test_create_level_summary_level_1(self, mock_mkdir, mock_file): mock_mkdir.assert_called_once_with(self.mock_output_path / "level-1-controls") # Verify file was opened for writing - mock_file.assert_called_once_with(self.mock_output_path / "level-1-controls/index.md", "w", encoding="utf-8") + mock_file.assert_called_once_with( + self.mock_output_path / "level-1-controls/index.md", "w", encoding="utf-8" + ) # Get the file handle handle = mock_file() @@ -126,7 +128,9 @@ def test_create_link_list_multiple_codes(self): result = asvs.create_link_list(requirements, "3.9") expected = ( - "[120](/taxonomy/capec-3.9/120), " "[126](/taxonomy/capec-3.9/126), " "[152](/taxonomy/capec-3.9/152)" + "[120](/taxonomy/capec-3.9/120), " + "[126](/taxonomy/capec-3.9/126), " + "[152](/taxonomy/capec-3.9/152)" ) self.assertEqual(result, expected) @@ -157,7 +161,10 @@ def test_create_link_list_sorted(self): requirements = {"capec_codes": ["152", "120", "126"]} result = asvs.create_link_list(requirements, "3.9") - expected = "[120](/taxonomy/capec-3.9/120), " "[126](/taxonomy/capec-3.9/126), [152](/taxonomy/capec-3.9/152)" + expected = ( + "[120](/taxonomy/capec-3.9/120), " + "[126](/taxonomy/capec-3.9/126), [152](/taxonomy/capec-3.9/152)" + ) self.assertEqual(result, expected) @@ -170,7 +177,9 @@ def test_parse_arguments_with_defaults(self): self.assertEqual(Path(args.output_path), asvs.ConvertVars.DEFAULT_OUTPUT_PATH) self.assertEqual(Path(args.input_path), asvs.ConvertVars.DEFAULT_INPUT_PATH) - self.assertEqual(Path(args.asvs_to_capec), asvs.ConvertVars.DEFAULT_ASVS_TO_CAPEC_INPUT_PATH) + self.assertEqual( + Path(args.asvs_to_capec), asvs.ConvertVars.DEFAULT_ASVS_TO_CAPEC_INPUT_PATH + ) self.assertEqual(args.capec_version, "3.9") self.assertFalse(args.debug) @@ -369,7 +378,11 @@ def test_load_json_file_with_invalid_json(self, mock_file): class TestLoadAsvsToCapecMapping(unittest.TestCase): """Test load_asvs_to_capec_mapping function""" - @patch("builtins.open", new_callable=mock_open, read_data='1.1.1:\n capec_codes:\n - "120"\n - "126"') + @patch( + "builtins.open", + new_callable=mock_open, + read_data='1.1.1:\n capec_codes:\n - "120"\n - "126"', + ) @patch("yaml.safe_load") def test_load_asvs_to_capec_mapping_success(self, mock_yaml_load, mock_file): """Test successfully loading YAML mapping file""" @@ -467,12 +480,16 @@ def test_create_level_obj(self): "L": "1", } - result = asvs._create_level_obj(subitem, "01-encoding", "01-architecture", "5.0") + result = asvs._create_level_obj( + subitem, "01-encoding", "01-architecture", "5.0" + ) self.assertEqual(result["topic"], "01-encoding") self.assertEqual(result["cat"], "01-architecture") self.assertEqual(result["name"], "V1.1.1") - self.assertEqual(result["link"], "/taxonomy/asvs-5.0/01-encoding/01-architecture#V1.1.1") + self.assertEqual( + result["link"], "/taxonomy/asvs-5.0/01-encoding/01-architecture#V1.1.1" + ) self.assertEqual(result["description"], "Verify that input is decoded") @@ -574,11 +591,15 @@ def test_process_requirement_item(self, mock_mkdir, mock_file): item = { "Ordinal": 1, "Name": "Architecture", - "Items": [{"Shortcode": "V1.1.1", "Description": "Test requirement", "L": "1"}], + "Items": [ + {"Shortcode": "V1.1.1", "Description": "Test requirement", "L": "1"} + ], } L1, L2, L3 = [], [], [] - asvs._process_requirement_item(item, "01-encoding", {}, "3.9", L1, L2, L3, "5.0") + asvs._process_requirement_item( + item, "01-encoding", {}, "3.9", L1, L2, L3, "5.0" + ) # Verify directory was created mock_mkdir.assert_called_once() @@ -677,7 +698,11 @@ def test_create_asvs_pages_with_multiple_levels(self, mock_mkdir, mock_file): mkdir_calls = [str(call_args[0][0]) for call_args in mock_mkdir.call_args_list] # Should include level control directories - level_dirs = [call for call in mkdir_calls if "level-" in str(call) and "-controls" in str(call)] + level_dirs = [ + call + for call in mkdir_calls + if "level-" in str(call) and "-controls" in str(call) + ] self.assertEqual(len(level_dirs), 3) # L1, L2, L3 @patch("builtins.open", new_callable=mock_open) diff --git a/tests/scripts/convert_capec_itest.py b/tests/scripts/convert_capec_itest.py index f318e465f..43638021d 100644 --- a/tests/scripts/convert_capec_itest.py +++ b/tests/scripts/convert_capec_itest.py @@ -25,7 +25,9 @@ def setUp(self) -> None: self.base_path = Path(__file__).parent.parent.parent.resolve() - self.test_input_file = self.base_path / "tests" / "test_files" / "capec-3.9" / "3000.json" + self.test_input_file = ( + self.base_path / "tests" / "test_files" / "capec-3.9" / "3000.json" + ) self.test_asvs_mapping = ( self.base_path / "tests" @@ -33,17 +35,25 @@ def setUp(self) -> None: / "asvs-5.0" / "OWASP_Application_Security_Verification_Standard_5.0.0_en.json" ) - self.test_capec_to_asvs = self.base_path / "tests" / "test_files" / "source" / "webapp-capec-3.0.yaml" + self.test_capec_to_asvs = ( + self.base_path / "tests" / "test_files" / "source" / "webapp-capec-3.0.yaml" + ) self.temp_output_dir = tempfile.mkdtemp() self.test_output_path = Path(self.temp_output_dir) if not self.test_input_file.exists(): - raise FileNotFoundError(f"Test input file not found: {self.test_input_file}") + raise FileNotFoundError( + f"Test input file not found: {self.test_input_file}" + ) if not self.test_asvs_mapping.exists(): - raise FileNotFoundError(f"Test ASVS mapping file not found: {self.test_asvs_mapping}") + raise FileNotFoundError( + f"Test ASVS mapping file not found: {self.test_asvs_mapping}" + ) if not self.test_capec_to_asvs.exists(): - raise FileNotFoundError(f"Test CAPEC to ASVS mapping file not found: {self.test_capec_to_asvs}") + raise FileNotFoundError( + f"Test CAPEC to ASVS mapping file not found: {self.test_capec_to_asvs}" + ) def tearDown(self) -> None: """Clean up test output directory""" @@ -85,9 +95,13 @@ def test_create_capec_pages_from_test_data(self): capec_to_asvs_map = capec.load_capec_to_asvs_mapping(self.test_capec_to_asvs) capec.create_capec_pages(data, capec_to_asvs_map, asvs_map, "5.0") - attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"]["Attack_Pattern"] + attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"][ + "Attack_Pattern" + ] - self.assertGreater(len(attack_patterns), 0, "Should have at least one attack pattern") + self.assertGreater( + len(attack_patterns), 0, "Should have at least one attack pattern" + ) first_pattern = attack_patterns[0] first_id = str(first_pattern["_ID"]) @@ -96,8 +110,12 @@ def test_create_capec_pages_from_test_data(self): pattern_dir = self.test_output_path / first_id index_file = pattern_dir / "index.md" - self.assertTrue(pattern_dir.exists(), f"Directory for CAPEC-{first_id} should exist") - self.assertTrue(index_file.exists(), f"index.md for CAPEC-{first_id} should exist") + self.assertTrue( + pattern_dir.exists(), f"Directory for CAPEC-{first_id} should exist" + ) + self.assertTrue( + index_file.exists(), f"index.md for CAPEC-{first_id} should exist" + ) content = index_file.read_text(encoding="utf-8") self.assertIn(f"CAPEC™ {first_id}", content) @@ -109,7 +127,9 @@ def test_all_attack_patterns_created(self): """Test that all attack patterns in test data are converted""" data = capec.load_json_file(self.test_input_file) - attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"]["Attack_Pattern"] + attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"][ + "Attack_Pattern" + ] capec.convert_vars.args = argparse.Namespace( output_path=self.test_output_path, @@ -129,7 +149,9 @@ def test_all_attack_patterns_created(self): pattern_id = str(pattern["_ID"]) index_file = self.test_output_path / pattern_id / "index.md" - self.assertTrue(index_file.exists(), f"index.md should exist for CAPEC-{pattern_id}") + self.assertTrue( + index_file.exists(), f"index.md should exist for CAPEC-{pattern_id}" + ) def test_created_file_content_structure(self): """Test that created markdown files have correct structure""" @@ -150,7 +172,9 @@ def test_created_file_content_structure(self): capec_to_asvs_map = capec.load_capec_to_asvs_mapping(self.test_capec_to_asvs) capec.create_capec_pages(data, capec_to_asvs_map, asvs_map, "5.0") - attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"]["Attack_Pattern"] + attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"][ + "Attack_Pattern" + ] pattern = attack_patterns[0] pattern_id = str(pattern["_ID"]) pattern_name = pattern["_Name"] @@ -160,7 +184,9 @@ def test_created_file_content_structure(self): lines = content.split("\n") - self.assertTrue(lines[0].startswith("# CAPEC™ "), "First line should be title with CAPEC-") + self.assertTrue( + lines[0].startswith("# CAPEC™ "), "First line should be title with CAPEC-" + ) self.assertIn(pattern_name, lines[0]) self.assertIn("## Description", content) @@ -239,7 +265,9 @@ def test_full_conversion_workflow(self): with self.assertLogs(logging.getLogger(), logging.INFO): capec.create_capec_pages(data, capec_to_asvs_map, asvs_map, "5.0") - attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"]["Attack_Pattern"] + attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"][ + "Attack_Pattern" + ] for pattern in attack_patterns: pattern_id = str(pattern["_ID"]) @@ -250,14 +278,20 @@ def test_description_parsing_variations(self): """Test that different description formats in test data are parsed correctly""" data = capec.load_json_file(self.test_input_file) - attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"]["Attack_Pattern"] + attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"][ + "Attack_Pattern" + ] for pattern in attack_patterns: description = pattern.get("Description", "") parsed = capec.parse_description(description) self.assertIsInstance(parsed, str) - self.assertGreater(len(parsed), 0, f"Description should not be empty for CAPEC-{pattern['_ID']}") + self.assertGreater( + len(parsed), + 0, + f"Description should not be empty for CAPEC-{pattern['_ID']}", + ) def test_load_capec_to_asvs_mapping(self): """Test loading CAPEC to ASVS mapping YAML file""" @@ -377,7 +411,11 @@ def test_no_asvs_mapping_output(self): if capec_5_file.exists(): content = capec_5_file.read_text(encoding="utf-8") # Should not have ASVS section if mapping is empty - lines_with_asvs = [line for line in content.split("\n") if "## Related ASVS Requirements" in line] + lines_with_asvs = [ + line + for line in content.split("\n") + if "## Related ASVS Requirements" in line + ] # Either no section, or empty section self.assertTrue(len(lines_with_asvs) == 0 or "ASVS (5.0):" not in content) @@ -388,7 +426,9 @@ class TestConvertCAPECWithOutputDirectory(unittest.TestCase): def setUp(self) -> None: """Set up test environment""" self.base_path = Path(__file__).parent.parent.parent.resolve() - self.test_input_file = self.base_path / "tests" / "test_files" / "capec-3.9" / "3000.json" + self.test_input_file = ( + self.base_path / "tests" / "test_files" / "capec-3.9" / "3000.json" + ) self.test_asvs_mapping = ( self.base_path / "tests" @@ -396,7 +436,9 @@ def setUp(self) -> None: / "asvs-5.0" / "OWASP_Application_Security_Verification_Standard_5.0.0_en.json" ) - self.test_capec_to_asvs = self.base_path / "tests" / "test_files" / "source" / "webapp-capec-3.0.yaml" + self.test_capec_to_asvs = ( + self.base_path / "tests" / "test_files" / "source" / "webapp-capec-3.0.yaml" + ) # Use the actual test output directory (create if doesn't exist) self.test_output_base = self.base_path / "tests" / "test_files" / "output" @@ -433,7 +475,9 @@ def test_conversion_to_test_output_directory(self): capec.create_capec_pages(data, capec_to_asvs_map, asvs_map, "5.0") - attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"]["Attack_Pattern"] + attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"][ + "Attack_Pattern" + ] first_pattern_id = str(attack_patterns[0]["_ID"]) output_file = self.test_output_path / first_pattern_id / "index.md" diff --git a/tests/scripts/convert_capec_map_to_asvs_map_itest.py b/tests/scripts/convert_capec_map_to_asvs_map_itest.py index 0a2d6e5aa..b7943d555 100644 --- a/tests/scripts/convert_capec_map_to_asvs_map_itest.py +++ b/tests/scripts/convert_capec_map_to_asvs_map_itest.py @@ -25,16 +25,26 @@ def setUp(self) -> None: self.base_path = Path(__file__).parent.parent.parent.resolve() # Set up test input file path - self.test_input_file = self.base_path / "tests" / "test_files" / "source" / "webapp-mappings-3.0.yaml" + self.test_input_file = ( + self.base_path + / "tests" + / "test_files" + / "source" + / "webapp-mappings-3.0.yaml" + ) # Create temporary output directory self.temp_output_dir = tempfile.mkdtemp() - self.test_capec_output_file = Path(self.temp_output_dir) / "webapp-capec-3.0.yaml" + self.test_capec_output_file = ( + Path(self.temp_output_dir) / "webapp-capec-3.0.yaml" + ) self.test_asvs_output_file = Path(self.temp_output_dir) / "webapp-asvs-3.0.yaml" # Verify test input file exists if not self.test_input_file.exists(): - raise FileNotFoundError(f"Test input file not found: {self.test_input_file}") + raise FileNotFoundError( + f"Test input file not found: {self.test_input_file}" + ) def tearDown(self) -> None: """Clean up test output directory""" @@ -60,12 +70,18 @@ def test_extract_capec_mappings_from_test_data(self): # Verify we extracted some mappings self.assertIsInstance(capec_mapping, dict) - self.assertGreater(len(capec_mapping), 0, "Should extract at least one CAPEC mapping") + self.assertGreater( + len(capec_mapping), 0, "Should extract at least one CAPEC mapping" + ) # Verify the structure of extracted data for capec_code, asvs_set in capec_mapping.items(): - self.assertIsInstance(capec_code, int, f"CAPEC code {capec_code} should be int") - self.assertIsInstance(asvs_set, set, f"ASVS set for {capec_code} should be a set") + self.assertIsInstance( + capec_code, int, f"CAPEC code {capec_code} should be int" + ) + self.assertIsInstance( + asvs_set, set, f"ASVS set for {capec_code} should be a set" + ) def test_extract_asvs_to_capec_mappings_from_test_data(self): """Test extracting ASVS to CAPEC mappings from real test data""" @@ -76,15 +92,23 @@ def test_extract_asvs_to_capec_mappings_from_test_data(self): # Verify we extracted some mappings self.assertIsInstance(asvs_mapping, dict) - self.assertGreater(len(asvs_mapping), 0, "Should extract at least one ASVS mapping") + self.assertGreater( + len(asvs_mapping), 0, "Should extract at least one ASVS mapping" + ) # Verify the structure of extracted data for asvs_req, capec_set in asvs_mapping.items(): - self.assertIsInstance(asvs_req, str, f"ASVS requirement {asvs_req} should be string") - self.assertIsInstance(capec_set, set, f"CAPEC set for {asvs_req} should be a set") + self.assertIsInstance( + asvs_req, str, f"ASVS requirement {asvs_req} should be string" + ) + self.assertIsInstance( + capec_set, set, f"CAPEC set for {asvs_req} should be a set" + ) # Each CAPEC code in the set should be an integer for capec_code in capec_set: - self.assertIsInstance(capec_code, str, f"CAPEC code {capec_code} should be int") + self.assertIsInstance( + capec_code, str, f"CAPEC code {capec_code} should be int" + ) def test_convert_to_output_format_with_test_data(self): """Test converting extracted mappings to output format""" @@ -104,7 +128,10 @@ def test_convert_to_output_format_with_test_data(self): def test_save_yaml_file_integration(self): """Test saving YAML file""" - test_data = {54: {"owasp_asvs": ["4.3.2", "13.2.2", "13.4.1"]}, 116: {"owasp_asvs": ["15.2.3", "16.2.5"]}} + test_data = { + 54: {"owasp_asvs": ["4.3.2", "13.2.2", "13.4.1"]}, + 116: {"owasp_asvs": ["15.2.3", "16.2.5"]}, + } # Save the file success = capec_map.save_yaml_file(self.test_capec_output_file, test_data) @@ -153,7 +180,9 @@ def test_full_conversion_pipeline(self): self.assertGreater(len(asvs_mapping), 0) # Convert to output format with capec_codes parameter - output_data_asvs = capec_map.convert_to_output_format(asvs_mapping, parameter="capec_codes") + output_data_asvs = capec_map.convert_to_output_format( + asvs_mapping, parameter="capec_codes" + ) # Save ASVS output success = capec_map.save_yaml_file(self.test_asvs_output_file, output_data_asvs) @@ -181,7 +210,9 @@ def test_specific_capec_codes_in_test_data(self): expected_capec_codes = [54, 116, 143, 144] for code in expected_capec_codes: - self.assertIn(code, capec_mapping, f"CAPEC code {code} should be in mappings") + self.assertIn( + code, capec_mapping, f"CAPEC code {code} should be in mappings" + ) def test_specific_asvs_requirements_in_test_data(self): """Test that ASVS requirements from test data are extracted correctly""" @@ -194,7 +225,11 @@ def test_specific_asvs_requirements_in_test_data(self): # Each ASVS requirement should map to one or more CAPEC codes for asvs_req, capec_codes in asvs_mapping.items(): self.assertIsInstance(capec_codes, set) - self.assertGreater(len(capec_codes), 0, f"ASVS {asvs_req} should map to at least one CAPEC code") + self.assertGreater( + len(capec_codes), + 0, + f"ASVS {asvs_req} should map to at least one CAPEC code", + ) def test_asvs_requirements_merged_correctly(self): """Test that ASVS requirements are merged correctly for duplicate CAPEC codes""" @@ -207,14 +242,20 @@ def test_asvs_requirements_merged_correctly(self): # Convert to list to check for duplicates asvs_list = list(asvs_set) self.assertEqual( - len(asvs_list), len(set(asvs_list)), f"CAPEC code {capec_code} has duplicate ASVS requirements" + len(asvs_list), + len(set(asvs_list)), + f"CAPEC code {capec_code} has duplicate ASVS requirements", ) def test_output_to_expected_location(self): """Test saving both output files to the expected test location""" # Set up the expected output paths - expected_capec_output_path = self.base_path / "tests" / "test_files" / "output" / "webapp-capec-3.0.yaml" - expected_asvs_output_path = self.base_path / "tests" / "test_files" / "output" / "webapp-asvs-3.0.yaml" + expected_capec_output_path = ( + self.base_path / "tests" / "test_files" / "output" / "webapp-capec-3.0.yaml" + ) + expected_asvs_output_path = ( + self.base_path / "tests" / "test_files" / "output" / "webapp-asvs-3.0.yaml" + ) # Create output directory if it doesn't exist expected_capec_output_path.parent.mkdir(parents=True, exist_ok=True) @@ -238,7 +279,9 @@ def test_output_to_expected_location(self): # Process ASVS mappings asvs_mapping = capec_map.extract_asvs_to_capec_mappings(data) - output_data_asvs = capec_map.convert_to_output_format(asvs_mapping, parameter="capec_codes") + output_data_asvs = capec_map.convert_to_output_format( + asvs_mapping, parameter="capec_codes" + ) # Save ASVS to expected location success = capec_map.save_yaml_file(expected_asvs_output_path, output_data_asvs) @@ -304,7 +347,9 @@ def test_main_function_with_test_files(self): # Extract and convert ASVS mappings asvs_mapping = capec_map.extract_asvs_to_capec_mappings(data) - output_data_asvs = capec_map.convert_to_output_format(asvs_mapping, parameter="capec_codes") + output_data_asvs = capec_map.convert_to_output_format( + asvs_mapping, parameter="capec_codes" + ) # Save ASVS output success = capec_map.save_yaml_file(self.test_asvs_output_file, output_data_asvs) @@ -325,7 +370,9 @@ def test_output_yaml_is_valid(self): # Test ASVS output asvs_mapping = capec_map.extract_asvs_to_capec_mappings(data) - output_data_asvs = capec_map.convert_to_output_format(asvs_mapping, parameter="capec_codes") + output_data_asvs = capec_map.convert_to_output_format( + asvs_mapping, parameter="capec_codes" + ) capec_map.save_yaml_file(self.test_asvs_output_file, output_data_asvs) reloaded_asvs_data = capec_map.load_yaml_file(self.test_asvs_output_file) self.assertEqual(reloaded_asvs_data, output_data_asvs) diff --git a/tests/scripts/convert_capec_map_to_asvs_map_utest.py b/tests/scripts/convert_capec_map_to_asvs_map_utest.py index 44831ede6..c9c2b92e6 100644 --- a/tests/scripts/convert_capec_map_to_asvs_map_utest.py +++ b/tests/scripts/convert_capec_map_to_asvs_map_utest.py @@ -12,7 +12,9 @@ class ConvertVars: OUTPUT_DIR: str = str(Path(__file__).parent.parent.resolve() / "/test_files/output") - OUTPUT_FILE: str = str(Path(__file__).parent.parent.resolve() / OUTPUT_DIR / "capec_to_asvs_map.yaml") + OUTPUT_FILE: str = str( + Path(__file__).parent.parent.resolve() / OUTPUT_DIR / "capec_to_asvs_map.yaml" + ) if "unittest.util" in __import__("sys").modules: @@ -25,7 +27,19 @@ class TestExtractAsvsToCapecMappings(unittest.TestCase): def test_extract_simple_asvs_mapping(self): """Test extracting a simple ASVS to CAPEC mapping""" - data = {"suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2", "13.2.2", "13.4.1"]}}}]}]} + data = { + "suits": [ + { + "cards": [ + { + "capec_map": { + 54: {"owasp_asvs": ["4.3.2", "13.2.2", "13.4.1"]} + } + } + ] + } + ] + } result = capec_map.extract_asvs_to_capec_mappings(data) self.assertIn("4.3.2", result) @@ -56,7 +70,16 @@ def test_extract_multiple_asvs_requirements(self): """Test extracting multiple ASVS requirements""" data = { "suits": [ - {"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}, 116: {"owasp_asvs": ["13.2.2", "15.2.3"]}}}]} + { + "cards": [ + { + "capec_map": { + 54: {"owasp_asvs": ["4.3.2"]}, + 116: {"owasp_asvs": ["13.2.2", "15.2.3"]}, + } + } + ] + } ] } result = capec_map.extract_asvs_to_capec_mappings(data) @@ -89,7 +112,19 @@ class TestExtractCapecMappings(unittest.TestCase): def test_extract_simple_capec_mapping(self): """Test extracting a simple CAPEC mapping""" - data = {"suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2", "13.2.2", "13.4.1"]}}}]}]} + data = { + "suits": [ + { + "cards": [ + { + "capec_map": { + 54: {"owasp_asvs": ["4.3.2", "13.2.2", "13.4.1"]} + } + } + ] + } + ] + } result = capec_map.extract_capec_mappings(data) self.assertIn(54, result) @@ -117,7 +152,16 @@ def test_extract_multiple_capec_codes(self): """Test extracting multiple CAPEC codes""" data = { "suits": [ - {"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}, 116: {"owasp_asvs": ["13.2.2", "15.2.3"]}}}]} + { + "cards": [ + { + "capec_map": { + 54: {"owasp_asvs": ["4.3.2"]}, + 116: {"owasp_asvs": ["13.2.2", "15.2.3"]}, + } + } + ] + } ] } result = capec_map.extract_capec_mappings(data) @@ -224,7 +268,9 @@ def test_convert_sorted_keys(self): def test_convert_with_capec_codes_parameter(self): """Test converting ASVS to CAPEC mapping with capec_codes parameter""" asvs_mapping = {"4.3.2": {54, 116}, "13.2.2": {54}} - result = capec_map.convert_to_output_format(asvs_mapping, parameter="capec_codes") + result = capec_map.convert_to_output_format( + asvs_mapping, parameter="capec_codes" + ) self.assertIn("4.3.2", result) self.assertIn("capec_codes", result["4.3.2"]) @@ -250,7 +296,9 @@ def test_load_valid_yaml(self, mock_yaml_load, mock_file): def test_load_file_not_found(self, mock_file): """Test loading non-existent file""" with self.assertLogs(logging.getLogger(), logging.ERROR) as log: - result = capec_map.load_yaml_file(Path(ConvertVars.OUTPUT_DIR + "nonexistent.yaml")) + result = capec_map.load_yaml_file( + Path(ConvertVars.OUTPUT_DIR + "nonexistent.yaml") + ) self.assertEqual(result, {}) self.assertIn("File not found", log.output[0]) @@ -260,7 +308,9 @@ def test_load_file_not_found(self, mock_file): def test_load_yaml_error(self, mock_yaml_load, mock_file): """Test loading file with YAML error""" with self.assertLogs(logging.getLogger(), logging.ERROR) as log: - result = capec_map.load_yaml_file(Path(ConvertVars.OUTPUT_DIR + "invalid.yaml")) + result = capec_map.load_yaml_file( + Path(ConvertVars.OUTPUT_DIR + "invalid.yaml") + ) self.assertEqual(result, {}) self.assertIn("Error loading YAML file", log.output[0]) @@ -295,7 +345,9 @@ def test_save_io_error(self, mock_file): data = {"key": "value"} with self.assertLogs(logging.getLogger(), logging.ERROR) as log: - result = capec_map.save_yaml_file(Path(ConvertVars.OUTPUT_DIR + "error.yaml"), data) + result = capec_map.save_yaml_file( + Path(ConvertVars.OUTPUT_DIR + "error.yaml"), data + ) self.assertFalse(result) self.assertIn("Error saving YAML file", log.output[0]) @@ -425,7 +477,9 @@ class TestMainFunction(unittest.TestCase): @patch("scripts.convert_capec_map_to_asvs_map.load_yaml_file") @patch("scripts.convert_capec_map_to_asvs_map.parse_arguments") @patch("sys.exit") - def test_main_successful_execution(self, mock_exit, mock_parse_args, mock_load, mock_save): + def test_main_successful_execution( + self, mock_exit, mock_parse_args, mock_load, mock_save + ): """Test successful main execution""" # Setup mocks mock_parse_args.return_value = argparse.Namespace( @@ -436,7 +490,9 @@ def test_main_successful_execution(self, mock_exit, mock_parse_args, mock_load, asvs_json=None, debug=False, ) - mock_load.return_value = {"suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}}}]}]} + mock_load.return_value = { + "suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}}}]}] + } mock_save.return_value = True capec_map.main() @@ -484,7 +540,9 @@ def test_main_save_fails(self, mock_exit2, mock_parse_args, mock_load, mock_save asvs_json=None, debug=False, ) - mock_load.return_value = {"suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}}}]}]} + mock_load.return_value = { + "suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}}}]}] + } mock_save.return_value = False with self.assertLogs(logging.getLogger(), logging.ERROR): @@ -501,7 +559,9 @@ def test_main_save_fails(self, mock_exit2, mock_parse_args, mock_load, mock_save @patch("scripts.convert_capec_map_to_asvs_map.load_yaml_file") @patch("scripts.convert_capec_map_to_asvs_map.parse_arguments") @patch("sys.exit") - def test_main_with_asvs_json(self, mock_exit, mock_parse_args, mock_load_yaml, mock_load_json, mock_save): + def test_main_with_asvs_json( + self, mock_exit, mock_parse_args, mock_load_yaml, mock_load_json, mock_save + ): """Test main execution with asvs_json argument""" mock_parse_args.return_value = argparse.Namespace( input_path=Path("input.yaml"), @@ -525,7 +585,9 @@ def test_main_with_asvs_json(self, mock_exit, mock_parse_args, mock_load_yaml, m ], } mock_load_json.return_value = { - "Requirements": [{"Shortcode": "V4.3.2", "Description": "Test description", "L": "L1"}] + "Requirements": [ + {"Shortcode": "V4.3.2", "Description": "Test description", "L": "L1"} + ] } mock_save.return_value = True @@ -552,7 +614,9 @@ def test_main_with_asvs_json_load_fails( asvs_json="bad_asvs.json", debug=False, ) - mock_load_yaml.return_value = {"suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}}}]}]} + mock_load_yaml.return_value = { + "suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}}}]}] + } mock_load_json.return_value = {} mock_save.return_value = True @@ -576,7 +640,9 @@ def test_main_no_input_path(self, mock_exit, mock_parse_args, mock_load, mock_sa asvs_json=None, debug=False, ) - mock_load.return_value = {"suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}}}]}]} + mock_load.return_value = { + "suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}}}]}] + } mock_save.return_value = True capec_map.main() @@ -589,7 +655,9 @@ def test_main_no_input_path(self, mock_exit, mock_parse_args, mock_load, mock_sa @patch("scripts.convert_capec_map_to_asvs_map.load_yaml_file") @patch("scripts.convert_capec_map_to_asvs_map.parse_arguments") @patch("sys.exit") - def test_main_asvs_save_fails(self, mock_exit, mock_parse_args, mock_load, mock_save): + def test_main_asvs_save_fails( + self, mock_exit, mock_parse_args, mock_load, mock_save + ): """Test main when the second save (ASVS output) fails""" mock_parse_args.return_value = argparse.Namespace( input_path=Path("input.yaml"), @@ -599,7 +667,9 @@ def test_main_asvs_save_fails(self, mock_exit, mock_parse_args, mock_load, mock_ asvs_json=None, debug=False, ) - mock_load.return_value = {"suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}}}]}]} + mock_load.return_value = { + "suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}}}]}] + } # First save succeeds, second save fails mock_save.side_effect = [True, False] @@ -653,7 +723,11 @@ class TestExtractAsvsDetails(unittest.TestCase): def test_extract_simple_requirement(self): """Test extracting a single requirement""" - data = {"Requirements": [{"Shortcode": "V1.1.1", "Description": "Test req", "L": "L1"}]} + data = { + "Requirements": [ + {"Shortcode": "V1.1.1", "Description": "Test req", "L": "L1"} + ] + } result = capec_map.extract_asvs_details(data) self.assertEqual(result, {"1.1.1": {"description": "Test req", "level": "L1"}}) @@ -672,13 +746,25 @@ def test_extract_multiple_requirements(self): def test_extract_nested_structure(self): """Test extracting from nested structure""" - data = {"chapter": {"sections": [{"items": [{"Shortcode": "V3.3.3", "Description": "Nested", "L": "L3"}]}]}} + data = { + "chapter": { + "sections": [ + { + "items": [ + {"Shortcode": "V3.3.3", "Description": "Nested", "L": "L3"} + ] + } + ] + } + } result = capec_map.extract_asvs_details(data) self.assertEqual(result, {"3.3.3": {"description": "Nested", "level": "L3"}}) def test_extract_shortcode_without_v_prefix(self): """Test extracting requirement without V prefix""" - data = {"Requirements": [{"Shortcode": "4.4.4", "Description": "No V", "L": "L1"}]} + data = { + "Requirements": [{"Shortcode": "4.4.4", "Description": "No V", "L": "L1"}] + } result = capec_map.extract_asvs_details(data) self.assertEqual(result, {"4.4.4": {"description": "No V", "level": "L1"}}) @@ -714,7 +800,9 @@ def test_convert_with_enrichment_data(self): """Test converting with enrichment data""" capec_map_data = {"1.1.1": {"4.3.2"}} enrichment = {"1.1.1": {"description": "Test desc", "level": "L1"}} - result = capec_map.convert_to_output_format(capec_map_data, parameter="capec_codes", enrichment_data=enrichment) + result = capec_map.convert_to_output_format( + capec_map_data, parameter="capec_codes", enrichment_data=enrichment + ) self.assertIn("1.1.1", result) self.assertEqual(result["1.1.1"]["description"], "Test desc") self.assertEqual(result["1.1.1"]["level"], "L1") @@ -724,7 +812,9 @@ def test_convert_with_partial_enrichment(self): """Test converting when enrichment data is missing for some keys""" capec_map_data = {"1.1.1": {"4.3.2"}, "2.2.2": {"5.5.5"}} enrichment = {"1.1.1": {"description": "Only first", "level": "L1"}} - result = capec_map.convert_to_output_format(capec_map_data, parameter="capec_codes", enrichment_data=enrichment) + result = capec_map.convert_to_output_format( + capec_map_data, parameter="capec_codes", enrichment_data=enrichment + ) self.assertIn("description", result["1.1.1"]) self.assertNotIn("description", result["2.2.2"]) @@ -734,7 +824,10 @@ def test_convert_with_meta_and_enrichment(self): enrichment = {"1.1.1": {"description": "Desc", "level": "L2"}} meta = {"edition": "webapp"} result = capec_map.convert_to_output_format( - capec_map_data, parameter="capec_codes", meta=meta, enrichment_data=enrichment + capec_map_data, + parameter="capec_codes", + meta=meta, + enrichment_data=enrichment, ) self.assertIn("meta", result) self.assertEqual(result["meta"]["edition"], "webapp") diff --git a/tests/scripts/convert_capec_utest.py b/tests/scripts/convert_capec_utest.py index fc0e0402a..63824cf42 100644 --- a/tests/scripts/convert_capec_utest.py +++ b/tests/scripts/convert_capec_utest.py @@ -28,7 +28,11 @@ def test_parse_description_with_list_of_paragraphs(self): """Test parsing description with list of paragraphs""" description = { "Description": { - "p": [{"__text": "First paragraph"}, {"__text": "Second paragraph"}, {"__text": "Third paragraph"}] + "p": [ + {"__text": "First paragraph"}, + {"__text": "Second paragraph"}, + {"__text": "Third paragraph"}, + ] } } result = capec.parse_description(description) @@ -37,7 +41,13 @@ def test_parse_description_with_list_of_paragraphs(self): def test_parse_description_with_mixed_list(self): """Test parsing description with mixed list content""" description = { - "Description": {"p": [{"__text": "First paragraph"}, "Plain string", {"__text": "Third paragraph"}]} + "Description": { + "p": [ + {"__text": "First paragraph"}, + "Plain string", + {"__text": "Third paragraph"}, + ] + } } result = capec.parse_description(description) self.assertEqual(result, "First paragraph Plain string Third paragraph") @@ -66,7 +76,9 @@ def test_validate_json_data_valid(self): """Test validation with valid data structure""" data = { "Attack_Pattern_Catalog": { - "Attack_Patterns": {"Attack_Pattern": [{"_ID": "1", "_Name": "Test Pattern"}]}, + "Attack_Patterns": { + "Attack_Pattern": [{"_ID": "1", "_Name": "Test Pattern"}] + }, "Categories": { "Category": [ { @@ -142,7 +154,11 @@ def test_validate_json_data_missing_attack_pattern(self): def test_validate_json_data_attack_pattern_not_list(self): """Test validation when Attack_Pattern is not a list""" - data = {"Attack_Pattern_Catalog": {"Attack_Patterns": {"Attack_Pattern": "not a list"}}} + data = { + "Attack_Pattern_Catalog": { + "Attack_Patterns": {"Attack_Pattern": "not a list"} + } + } with self.assertLogs(logging.getLogger(), logging.ERROR) as log: result = capec.validate_json_data(data) self.assertFalse(result) @@ -175,7 +191,9 @@ def test_load_json_file_invalid_json(self): mock_file = mock_open(read_data="invalid json content {") with patch("builtins.open", mock_file): - with patch("json.load", side_effect=json.JSONDecodeError("Invalid", "doc", 0)): + with patch( + "json.load", side_effect=json.JSONDecodeError("Invalid", "doc", 0) + ): with self.assertLogs(logging.getLogger(), logging.ERROR) as log: result = capec.load_json_file(Path("/test/file.json")) @@ -318,7 +336,11 @@ def test_create_capec_pages_single_pattern(self, mock_create_folder, mock_file): "Attack_Pattern_Catalog": { "Attack_Patterns": { "Attack_Pattern": [ - {"_ID": "123", "_Name": "Test Attack Pattern", "Description": "This is a test description"} + { + "_ID": "123", + "_Name": "Test Attack Pattern", + "Description": "This is a test description", + } ] }, "Categories": { @@ -350,7 +372,9 @@ def test_create_capec_pages_single_pattern(self, mock_create_folder, mock_file): with patch.object(Path, "parent") as mock_parent: mock_parent.resolve.return_value = Path("/mock/directory") - capec.create_capec_pages(test_data, test_capec_to_asvs_map, test_asvs_map, "5.0") + capec.create_capec_pages( + test_data, test_capec_to_asvs_map, test_asvs_map, "5.0" + ) # Verify create_folder was called mock_create_folder.assert_called() @@ -366,7 +390,10 @@ def test_create_capec_pages_single_pattern(self, mock_create_folder, mock_file): self.assertIn("CAPEC™ 123: Test Attack Pattern", written_content) self.assertIn("## Description", written_content) self.assertIn("This is a test description", written_content) - self.assertIn("Source: [CAPEC™ 123](https://capec.mitre.org/data/definitions/123.html)", written_content) + self.assertIn( + "Source: [CAPEC™ 123](https://capec.mitre.org/data/definitions/123.html)", + written_content, + ) @patch("builtins.open", new_callable=mock_open) @patch("scripts.convert_capec.create_folder") @@ -382,8 +409,16 @@ def test_create_capec_pages_multiple_patterns(self, mock_create_folder, mock_fil "Attack_Pattern_Catalog": { "Attack_Patterns": { "Attack_Pattern": [ - {"_ID": "1", "_Name": "First Pattern", "Description": "First description"}, - {"_ID": "2", "_Name": "Second Pattern", "Description": "Second description"}, + { + "_ID": "1", + "_Name": "First Pattern", + "Description": "First description", + }, + { + "_ID": "2", + "_Name": "Second Pattern", + "Description": "Second description", + }, ] }, "Categories": { @@ -415,7 +450,9 @@ def test_create_capec_pages_multiple_patterns(self, mock_create_folder, mock_fil with patch.object(Path, "parent") as mock_parent: mock_parent.resolve.return_value = Path("/mock/directory") - capec.create_capec_pages(test_data, test_capec_to_asvs_map, test_asvs_map, "5.0") + capec.create_capec_pages( + test_data, test_capec_to_asvs_map, test_asvs_map, "5.0" + ) # Verify create_folder was called twice (once for each pattern) self.assertEqual(mock_create_folder.call_count, 3) @@ -425,7 +462,9 @@ def test_create_capec_pages_multiple_patterns(self, mock_create_folder, mock_fil @patch("builtins.open", new_callable=mock_open) @patch("scripts.convert_capec.create_folder") - def test_create_capec_pages_complex_description(self, mock_create_folder, mock_file): + def test_create_capec_pages_complex_description( + self, mock_create_folder, mock_file + ): """Test creating CAPEC pages with complex description format""" # Setup test_output_path = Path("/test/output") @@ -440,7 +479,11 @@ def test_create_capec_pages_complex_description(self, mock_create_folder, mock_f { "_ID": "456", "_Name": "Complex Pattern", - "Description": {"Description": {"p": {"__text": "Complex description text"}}}, + "Description": { + "Description": { + "p": {"__text": "Complex description text"} + } + }, } ] }, @@ -473,7 +516,9 @@ def test_create_capec_pages_complex_description(self, mock_create_folder, mock_f with patch.object(Path, "parent") as mock_parent: mock_parent.resolve.return_value = Path("/mock/directory") - capec.create_capec_pages(test_data, test_capec_to_asvs_map, test_asvs_map, "5.0") + capec.create_capec_pages( + test_data, test_capec_to_asvs_map, test_asvs_map, "5.0" + ) # Get the written content handle = mock_file() @@ -535,7 +580,10 @@ class TestLoadCapecToAsvsMapping(unittest.TestCase): @patch("builtins.open", new_callable=mock_open) def test_load_capec_to_asvs_mapping_valid(self, mock_file, mock_yaml_load): """Test loading valid CAPEC to ASVS mapping""" - test_data = {1: {"owasp_asvs": ["V8.1.1", "V8.2.1"]}, 5: {"owasp_asvs": ["V1.2.9"]}} + test_data = { + 1: {"owasp_asvs": ["V8.1.1", "V8.2.1"]}, + 5: {"owasp_asvs": ["V1.2.9"]}, + } mock_yaml_load.return_value = test_data result = capec.load_capec_to_asvs_mapping(Path("test.yaml")) @@ -583,7 +631,11 @@ def test_create_link_list_with_requirements(self): "Name": "General Authorization Design", "Items": [{"Shortcode": "V8.1.1", "Text": "Test"}], }, - {"Ordinal": 2, "Name": "Operation Level", "Items": [{"Shortcode": "V8.2.1", "Text": "Test"}]}, + { + "Ordinal": 2, + "Name": "Operation Level", + "Items": [{"Shortcode": "V8.2.1", "Text": "Test"}], + }, ], } ] @@ -630,7 +682,9 @@ def test_createlink_found(self): { "Ordinal": 1, "Name": "General Authorization Design", - "Items": [{"Shortcode": "V8.1.1", "Text": "Test requirement"}], + "Items": [ + {"Shortcode": "V8.1.1", "Text": "Test requirement"} + ], } ], } @@ -756,7 +810,13 @@ def test_create_capec_pages_with_asvs_mapping(self, mock_create_folder, mock_fil test_data = { "Attack_Pattern_Catalog": { "Attack_Patterns": { - "Attack_Pattern": [{"_ID": "1", "_Name": "Test Pattern", "Description": "Test description"}] + "Attack_Pattern": [ + { + "_ID": "1", + "_Name": "Test Pattern", + "Description": "Test description", + } + ] }, "Categories": { "Category": [ @@ -790,8 +850,16 @@ def test_create_capec_pages_with_asvs_mapping(self, mock_create_folder, mock_fil "Ordinal": 8, "Name": "Authorization", "Items": [ - {"Ordinal": 1, "Name": "General Design", "Items": [{"Shortcode": "V8.1.1"}]}, - {"Ordinal": 2, "Name": "Operation Level", "Items": [{"Shortcode": "V8.2.1"}]}, + { + "Ordinal": 1, + "Name": "General Design", + "Items": [{"Shortcode": "V8.1.1"}], + }, + { + "Ordinal": 2, + "Name": "Operation Level", + "Items": [{"Shortcode": "V8.2.1"}], + }, ], } ] @@ -799,7 +867,9 @@ def test_create_capec_pages_with_asvs_mapping(self, mock_create_folder, mock_fil with patch.object(Path, "parent") as mock_parent: mock_parent.resolve.return_value = Path("/mock/directory") - capec.create_capec_pages(test_data, test_capec_to_asvs_map, test_asvs_map, "5.0") + capec.create_capec_pages( + test_data, test_capec_to_asvs_map, test_asvs_map, "5.0" + ) handle = mock_file() written_content = "".join(call.args[0] for call in handle.write.call_args_list) diff --git a/tests/scripts/convert_fuzzer.py b/tests/scripts/convert_fuzzer.py index 95ae27c24..a4b58d1c5 100644 --- a/tests/scripts/convert_fuzzer.py +++ b/tests/scripts/convert_fuzzer.py @@ -30,7 +30,18 @@ def test_main(data): "owasp_cornucopia_webapp_ver_guide_bridge_lang.docx", ] ) - args = ["-t", template, "-lt", "guide", "-l", "en", "-v", "1.22", "-i", template_docx_file] + args = [ + "-t", + template, + "-lt", + "guide", + "-l", + "en", + "-v", + "1.22", + "-i", + template_docx_file, + ] argp = { "debug": True, "pdf": False, @@ -53,12 +64,27 @@ def test_main(data): try: with patch.object(argparse, "ArgumentParser") as mock_parser: - mock_parser.return_value.parse_args.return_value = argparse.Namespace(**argp) + mock_parser.return_value.parse_args.return_value = argparse.Namespace( + **argp + ) with test.assertLogs(logging.getLogger(), logging.DEBUG) as l6: with patch("sys.argv", args): c.main() - args = ["-e", edition, "-t", "bridge", "-lt", "guide", "-l", "en", "-v", "1.22", "-i", template_docx_file] + args = [ + "-e", + edition, + "-t", + "bridge", + "-lt", + "guide", + "-l", + "en", + "-v", + "1.22", + "-i", + template_docx_file, + ] argp = { "debug": True, "pdf": False, @@ -71,12 +97,27 @@ def test_main(data): "outputfile": "", } - mock_parser.return_value.parse_args.return_value = argparse.Namespace(**argp) + mock_parser.return_value.parse_args.return_value = argparse.Namespace( + **argp + ) with test.assertLogs(logging.getLogger(), logging.DEBUG) as l6: with patch("sys.argv", args): c.main() - args = ["-e", "webapp", "-t", "bridge", "-lt", layout, "-l", "en", "-v", "1.22", "-i", template_docx_file] + args = [ + "-e", + "webapp", + "-t", + "bridge", + "-lt", + layout, + "-l", + "en", + "-v", + "1.22", + "-i", + template_docx_file, + ] argp = { "debug": True, "pdf": False, @@ -89,12 +130,27 @@ def test_main(data): "outputfile": "", } - mock_parser.return_value.parse_args.return_value = argparse.Namespace(**argp) + mock_parser.return_value.parse_args.return_value = argparse.Namespace( + **argp + ) with test.assertLogs(logging.getLogger(), logging.DEBUG) as l6: with patch("sys.argv", args): c.main() - args = ["-e", "webapp", "-t", "bridge", "-lt", "guide", "-l", lang, "-v", "1.22", "-i", template_docx_file] + args = [ + "-e", + "webapp", + "-t", + "bridge", + "-lt", + "guide", + "-l", + lang, + "-v", + "1.22", + "-i", + template_docx_file, + ] argp = { "debug": True, "pdf": False, @@ -107,12 +163,27 @@ def test_main(data): "outputfile": "", } - mock_parser.return_value.parse_args.return_value = argparse.Namespace(**argp) + mock_parser.return_value.parse_args.return_value = argparse.Namespace( + **argp + ) with test.assertLogs(logging.getLogger(), logging.DEBUG) as l6: with patch("sys.argv", args): c.main() - args = ["-e", "webapp", "-t", "bridge", "-lt", "guide", "-l", "en", "-v", version, "-i", template_docx_file] + args = [ + "-e", + "webapp", + "-t", + "bridge", + "-lt", + "guide", + "-l", + "en", + "-v", + version, + "-i", + template_docx_file, + ] argp = { "debug": True, "pdf": False, @@ -125,12 +196,27 @@ def test_main(data): "outputfile": "", } - mock_parser.return_value.parse_args.return_value = argparse.Namespace(**argp) + mock_parser.return_value.parse_args.return_value = argparse.Namespace( + **argp + ) with test.assertLogs(logging.getLogger(), logging.DEBUG) as l6: with patch("sys.argv", args): c.main() - args = ["-e", "webapp", "-t", "bridge", "-lt", "guide", "-l", lang, "-v", "1.22", "-i", template_docx_file] + args = [ + "-e", + "webapp", + "-t", + "bridge", + "-lt", + "guide", + "-l", + lang, + "-v", + "1.22", + "-i", + template_docx_file, + ] argp = { "debug": True, "pdf": False, @@ -143,7 +229,9 @@ def test_main(data): "outputfile": "", } - mock_parser.return_value.parse_args.return_value = argparse.Namespace(**argp) + mock_parser.return_value.parse_args.return_value = argparse.Namespace( + **argp + ) with test.assertLogs(logging.getLogger(), logging.DEBUG) as l6: with patch("sys.argv", args): c.main() @@ -176,12 +264,27 @@ def test_main(data): "outputfile": outputfile, } - mock_parser.return_value.parse_args.return_value = argparse.Namespace(**argp) + mock_parser.return_value.parse_args.return_value = argparse.Namespace( + **argp + ) with test.assertLogs(logging.getLogger(), logging.DEBUG) as l6: with patch("sys.argv", args): c.main() - args = ["-e", "webapp", "-t", "bridge", "-lt", "guide", "-l", "en", "-v", "1.22", "-i", inputtemplate] + args = [ + "-e", + "webapp", + "-t", + "bridge", + "-lt", + "guide", + "-l", + "en", + "-v", + "1.22", + "-i", + inputtemplate, + ] argp = { "debug": True, "pdf": False, @@ -194,7 +297,9 @@ def test_main(data): "outputfile": "", } - mock_parser.return_value.parse_args.return_value = argparse.Namespace(**argp) + mock_parser.return_value.parse_args.return_value = argparse.Namespace( + **argp + ) with test.assertLogs(logging.getLogger(), logging.DEBUG) as l6: with patch("sys.argv", args): c.main() diff --git a/tests/scripts/convert_itest.py b/tests/scripts/convert_itest.py index c45901fe8..aa5b4e1ec 100644 --- a/tests/scripts/convert_itest.py +++ b/tests/scripts/convert_itest.py @@ -26,7 +26,18 @@ def setUp(self) -> None: "owasp_cornucopia_webapp_ver_guide_bridge_lang.odt", ] ) - self.args = ["-t", "bridge", "-lt", "guide", "-l", "en", "-v", "3.0", "-i", self.template_docx_file] + self.args = [ + "-t", + "bridge", + "-lt", + "guide", + "-l", + "en", + "-v", + "3.0", + "-i", + self.template_docx_file, + ] self.argp = { "debug": False, "pdf": False, @@ -60,7 +71,9 @@ def tearDown(self) -> None: def test_main(self): with patch.object(argparse, "ArgumentParser") as mock_parser: - mock_parser.return_value.parse_args.return_value = argparse.Namespace(**self.argp) + mock_parser.return_value.parse_args.return_value = argparse.Namespace( + **self.argp + ) with self.assertLogs(logging.getLogger(), logging.INFO) as l6: with patch("sys.argv", self.args): c.main() diff --git a/tests/scripts/convert_utest.py b/tests/scripts/convert_utest.py index de2614c85..abf03996a 100644 --- a/tests/scripts/convert_utest.py +++ b/tests/scripts/convert_utest.py @@ -17,7 +17,9 @@ import scripts.convert as c c.convert_vars = c.ConvertVars() -c.convert_vars.BASE_PATH = os.path.split(os.path.dirname(os.path.realpath(c.__file__)))[0] +c.convert_vars.BASE_PATH = os.path.split(os.path.dirname(os.path.realpath(c.__file__)))[ + 0 +] if "unittest.util" in __import__("sys").modules: @@ -64,7 +66,11 @@ def test_is_valid_string_argument(self) -> None: ) ) # Arabic - self.assertTrue(c.is_valid_string_argument("ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوي ًٌٍَُِّْٰﷲﷴﷺﷻ ٠١٢٣٤٥٦٧٨٩ ")) + self.assertTrue( + c.is_valid_string_argument( + "ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوي ًٌٍَُِّْٰﷲﷴﷺﷻ ٠١٢٣٤٥٦٧٨٩ " + ) + ) # European self.assertTrue( c.is_valid_string_argument( @@ -218,7 +224,10 @@ def test_get_valid_language_choices_all(self) -> None: class TestSetCanConvertToPdf(unittest.TestCase): def test_set_can_convert_to_pdf(self) -> None: - want_can_convert_in = sys.platform.lower().find("win") != -1 or sys.platform.lower().find("darwin") != -1 + want_can_convert_in = ( + sys.platform.lower().find("win") != -1 + or sys.platform.lower().find("darwin") != -1 + ) c.set_can_convert_to_pdf() got_can_convert = c.convert_vars.can_convert_to_pdf @@ -320,7 +329,10 @@ def test_get_template_for_edition_default_docx(self) -> None: want_template_doc = os.path.normpath( os.path.join( - c.convert_vars.BASE_PATH, "resources", "templates", "owasp_cornucopia_webapp_ver_guide_bridge_lang.odt" + c.convert_vars.BASE_PATH, + "resources", + "templates", + "owasp_cornucopia_webapp_ver_guide_bridge_lang.odt", ) ) @@ -333,7 +345,10 @@ def test_get_template_for_edition_default_idml(self) -> None: edition = "webapp" want_template_doc = os.path.normpath( os.path.join( - c.convert_vars.BASE_PATH, "resources", "templates", "owasp_cornucopia_webapp_ver_cards_bridge_lang.idml" + c.convert_vars.BASE_PATH, + "resources", + "templates", + "owasp_cornucopia_webapp_ver_cards_bridge_lang.idml", ) ) @@ -345,10 +360,18 @@ def test_get_template_for_edition_relative_path(self) -> None: template = "bridge" edition = "webapp" c.convert_vars.args.inputfile = os.path.normpath( - os.path.join("..", "resources", "templates", "owasp_cornucopia_webapp_ver_guide_bridge_lang.odt") + os.path.join( + "..", + "resources", + "templates", + "owasp_cornucopia_webapp_ver_guide_bridge_lang.odt", + ) ) want_template_doc = os.path.join( - c.convert_vars.BASE_PATH, "resources", "templates", "owasp_cornucopia_webapp_ver_guide_bridge_lang.odt" + c.convert_vars.BASE_PATH, + "resources", + "templates", + "owasp_cornucopia_webapp_ver_guide_bridge_lang.odt", ) got_template_doc = c.get_template_for_edition(layout, template, edition) @@ -359,10 +382,17 @@ def test_get_template_for_edition_relative_path_root(self) -> None: template = "bridge" edition = "webapp" c.convert_vars.args.inputfile = os.path.normpath( - os.path.join("resources", "templates", "owasp_cornucopia_webapp_ver_guide_bridge_lang.odt") + os.path.join( + "resources", + "templates", + "owasp_cornucopia_webapp_ver_guide_bridge_lang.odt", + ) ) want_template_doc = os.path.join( - c.convert_vars.BASE_PATH, "resources", "templates", "owasp_cornucopia_webapp_ver_guide_bridge_lang.odt" + c.convert_vars.BASE_PATH, + "resources", + "templates", + "owasp_cornucopia_webapp_ver_guide_bridge_lang.odt", ) got_template_doc = c.get_template_for_edition(layout, template, edition) @@ -370,7 +400,9 @@ def test_get_template_for_edition_relative_path_root(self) -> None: def test_get_template_for_edition_file_not_exist(self) -> None: template_docx_filename = os.path.normpath( - os.path.join("resources", "templates", "owasp_cornucopia_template_template.docx") + os.path.join( + "resources", "templates", "owasp_cornucopia_template_template.docx" + ) ) c.convert_vars.args.inputfile = template_docx_filename layout = "guide" @@ -378,7 +410,8 @@ def test_get_template_for_edition_file_not_exist(self) -> None: edition = "webapp" want_template_doc = "None" want_error_log_messages = [ - f"ERROR:root:Source file not found: {template_docx_filename}. " "Please ensure file exists and try again." + f"ERROR:root:Source file not found: {template_docx_filename}. " + "Please ensure file exists and try again." ] with self.assertLogs(logging.getLogger(), logging.ERROR) as ll: @@ -389,30 +422,49 @@ def test_get_template_for_edition_file_not_exist(self) -> None: class TestRenameOutputFile(unittest.TestCase): def setUp(self) -> None: - c.convert_vars.args = argparse.Namespace(outputfile=c.convert_vars.DEFAULT_OUTPUT_FILENAME) - self.input_meta_data = {"edition": "webapp", "component": "cards", "language": "EN", "version": "3.0"} + c.convert_vars.args = argparse.Namespace( + outputfile=c.convert_vars.DEFAULT_OUTPUT_FILENAME + ) + self.input_meta_data = { + "edition": "webapp", + "component": "cards", + "language": "EN", + "version": "3.0", + } def tearDown(self) -> None: c.convert_vars.args.outputfile = "" def test_rename_output_file_short(self) -> None: - c.convert_vars.args.outputfile = os.path.join("output", "cornucopia_edition_ver_layout_lang.docx") + c.convert_vars.args.outputfile = os.path.join( + "output", "cornucopia_edition_ver_layout_lang.docx" + ) file_extension = ".docx" template = "bridge" layout = "guide" - want_filename = os.path.join(c.convert_vars.BASE_PATH, "output", "cornucopia_webapp_3.0_guide_en.docx") + want_filename = os.path.join( + c.convert_vars.BASE_PATH, "output", "cornucopia_webapp_3.0_guide_en.docx" + ) - got_filename = c.rename_output_file(file_extension, template, layout, self.input_meta_data) + got_filename = c.rename_output_file( + file_extension, template, layout, self.input_meta_data + ) self.assertEqual(want_filename, got_filename) def test_rename_output_file_no_extension(self) -> None: - c.convert_vars.args.outputfile = "output" + os.sep + "cornucopia_edition_ver_layout_lang" + c.convert_vars.args.outputfile = ( + "output" + os.sep + "cornucopia_edition_ver_layout_lang" + ) file_extension = ".idml" template = "bridge" layout = "guide" - want_filename = os.path.join(c.convert_vars.BASE_PATH, "output", "cornucopia_webapp_3.0_guide_en.idml") + want_filename = os.path.join( + c.convert_vars.BASE_PATH, "output", "cornucopia_webapp_3.0_guide_en.idml" + ) - got_filename = c.rename_output_file(file_extension, template, layout, self.input_meta_data) + got_filename = c.rename_output_file( + file_extension, template, layout, self.input_meta_data + ) self.assertEqual(want_filename, got_filename) def test_rename_output_file_using_defaults(self) -> None: @@ -421,10 +473,14 @@ def test_rename_output_file_using_defaults(self) -> None: template = "bridge" layout = "guide" want_filename = os.path.join( - c.convert_vars.BASE_PATH, "output", "owasp_cornucopia_webapp_3.0_guide_bridge_en.docx" + c.convert_vars.BASE_PATH, + "output", + "owasp_cornucopia_webapp_3.0_guide_bridge_en.docx", ) - got_filename = c.rename_output_file(file_extension, template, layout, self.input_meta_data) + got_filename = c.rename_output_file( + file_extension, template, layout, self.input_meta_data + ) self.assertEqual(want_filename, got_filename) def test_rename_output_file_blank(self) -> None: @@ -433,10 +489,14 @@ def test_rename_output_file_blank(self) -> None: template = "bridge" layout = "guide" want_filename = os.path.join( - c.convert_vars.BASE_PATH, "output", "owasp_cornucopia_webapp_3.0_guide_bridge_en.docx" + c.convert_vars.BASE_PATH, + "output", + "owasp_cornucopia_webapp_3.0_guide_bridge_en.docx", ) - got_filename = c.rename_output_file(file_extension, template, layout, self.input_meta_data) + got_filename = c.rename_output_file( + file_extension, template, layout, self.input_meta_data + ) self.assertEqual(want_filename, got_filename) def test_rename_output_file_template(self) -> None: @@ -445,10 +505,14 @@ def test_rename_output_file_template(self) -> None: template = "bridge" layout = "guide" want_filename = os.path.join( - c.convert_vars.BASE_PATH, "output", "owasp_cornucopia_webapp_3.0_guide_bridge_en.docx" + c.convert_vars.BASE_PATH, + "output", + "owasp_cornucopia_webapp_3.0_guide_bridge_en.docx", ) - got_filename = c.rename_output_file(file_extension, template, layout, self.input_meta_data) + got_filename = c.rename_output_file( + file_extension, template, layout, self.input_meta_data + ) self.assertEqual(want_filename, got_filename) @@ -461,7 +525,12 @@ def setUp(self) -> None: ("_lang", "_en"), ("_ver", "_3.0"), ] - self.input_meta_data = {"edition": "webapp", "component": "cards", "language": "EN", "version": "3.0"} + self.input_meta_data = { + "edition": "webapp", + "component": "cards", + "language": "EN", + "version": "3.0", + } def test_get_find_replace_list_default(self) -> None: want_list = self.want_list_default @@ -489,7 +558,9 @@ def test_valid_meta(self) -> None: "-> languages section in the mappings file" ) with self.assertLogs(logging.getLogger(), logging.WARNING) as ll: - valid: bool = c.valid_meta(self.meta, "fr", "webapp", "3.0", "bridge", "cards") + valid: bool = c.valid_meta( + self.meta, "fr", "webapp", "3.0", "bridge", "cards" + ) self.assertFalse(valid) self.assertIn(want_logging_error_message, " ".join(ll.output)) @@ -497,7 +568,12 @@ def test_valid_meta(self) -> None: class TestGetMetaData(unittest.TestCase): def setUp(self) -> None: self.test_data: Dict[str, Any] = { - "meta": {"edition": "webapp", "component": "cards", "language": "EN", "version": "3.0"}, + "meta": { + "edition": "webapp", + "component": "cards", + "language": "EN", + "version": "3.0", + }, "suits": [ { "id": "VE", @@ -521,7 +597,12 @@ def setUp(self) -> None: def test_get_meta_data_defaults(self) -> None: input_data = self.test_data.copy() - want_data = {"edition": "webapp", "component": "cards", "language": "EN", "version": "3.0"} + want_data = { + "edition": "webapp", + "component": "cards", + "language": "EN", + "version": "3.0", + } got_data = c.get_meta_data(input_data) self.assertDictEqual(want_data, got_data) @@ -530,7 +611,9 @@ def test_get_meta_data_failure(self) -> None: input_data = self.test_data.copy() del input_data["meta"] want_data: Dict[str, str] = {} - want_logging_error_message = ["ERROR:root:Could not find meta tag in the language data."] + want_logging_error_message = [ + "ERROR:root:Could not find meta tag in the language data." + ] with self.assertLogs(logging.getLogger(), logging.ERROR) as ll: got_data = c.get_meta_data(input_data) @@ -540,11 +623,18 @@ def test_get_meta_data_failure(self) -> None: class TestGetLanguageData(unittest.TestCase): def setUp(self) -> None: - test_source_yaml = os.path.join(c.convert_vars.BASE_PATH, "tests", "test_files", "source", "*.yaml") + test_source_yaml = os.path.join( + c.convert_vars.BASE_PATH, "tests", "test_files", "source", "*.yaml" + ) self.input_yaml_files = glob.glob(test_source_yaml) self.input_language = "en" self.test_data: Dict[str, Any] = { - "meta": {"edition": "webapp", "component": "cards", "language": "EN", "version": "3.0"}, + "meta": { + "edition": "webapp", + "component": "cards", + "language": "EN", + "version": "3.0", + }, "suits": [ { "id": "VE", @@ -591,7 +681,12 @@ def test_has_translation_for_edition(self) -> None: self.assertTrue(c.has_translation_for_edition({"languages": ["en"]}, "en")) def test_get_language_data_translation_meta(self) -> None: - want_meta = {"edition": "webapp", "component": "cards", "language": "EN", "version": "3.0"} + want_meta = { + "edition": "webapp", + "component": "cards", + "language": "EN", + "version": "3.0", + } got_data = c.get_language_data(self.input_yaml_files, self.input_language) self.assertEqual(want_meta, got_data["meta"]) @@ -601,13 +696,19 @@ def test_get_language_data_translation_en_first_suit_first_card(self) -> None: want_first_suit_first_card_keys = self.test_data["suits"][0]["cards"][0].keys() want_first_suit_first_card_value = self.test_data["suits"][0]["cards"][0]["id"] - got_suits = c.get_language_data(self.input_yaml_files, self.input_language)["suits"] + got_suits = c.get_language_data(self.input_yaml_files, self.input_language)[ + "suits" + ] got_first_suit_keys = got_suits[0].keys() self.assertEqual(want_first_suit_keys, got_first_suit_keys) got_first_suit_first_card_keys = got_suits[0]["cards"][0].keys() - self.assertEqual(want_first_suit_first_card_keys, got_first_suit_first_card_keys) + self.assertEqual( + want_first_suit_first_card_keys, got_first_suit_first_card_keys + ) got_first_suit_first_card_value = got_suits[0]["cards"][0]["id"] - self.assertEqual(want_first_suit_first_card_value, got_first_suit_first_card_value) + self.assertEqual( + want_first_suit_first_card_value, got_first_suit_first_card_value + ) def test_get_language_data_translation_es_first_suit_first_card(self) -> None: want_first_suit_keys = self.test_data["suits"][0].keys() @@ -618,9 +719,13 @@ def test_get_language_data_translation_es_first_suit_first_card(self) -> None: got_first_suit_keys = got_suits[0].keys() self.assertEqual(want_first_suit_keys, got_first_suit_keys) got_first_suit_first_card_keys = got_suits[0]["cards"][0].keys() - self.assertEqual(want_first_suit_first_card_keys, got_first_suit_first_card_keys) + self.assertEqual( + want_first_suit_first_card_keys, got_first_suit_first_card_keys + ) got_first_suit_first_card_value = got_suits[0]["cards"][0]["id"] - self.assertEqual(want_first_suit_first_card_value, got_first_suit_first_card_value) + self.assertEqual( + want_first_suit_first_card_value, got_first_suit_first_card_value + ) def test_get_mapping_data_for_edition(self) -> None: want_meta = { @@ -632,7 +737,9 @@ def test_get_mapping_data_for_edition(self) -> None: "layouts": ["cards", "leaflet", "guide"], "templates": ["bridge_qr", "bridge", "tarot", "tarot_qr"], } - got_data = c.get_mapping_data_for_edition(self.input_yaml_files, self.input_language, "3.0", "webapp") + got_data = c.get_mapping_data_for_edition( + self.input_yaml_files, self.input_language, "3.0", "webapp" + ) self.assertEqual(want_meta, got_data["meta"]) def test_get_mapping_data_for_edition_first_suit_first_card(self) -> None: @@ -692,7 +799,22 @@ def test_get_mapping_data_for_edition_first_suit_first_card(self) -> None: "16.5.1", "17.1.1", ], - "capec": [54, 113, 116, 143, 144, 149, 150, 155, 169, 215, 224, 497, 541, 546], + "capec": [ + 54, + 113, + 116, + 143, + 144, + 149, + 150, + 155, + 169, + 215, + 224, + 497, + 541, + 546, + ], "capec_map": { 54: { "owasp_asvs": [ @@ -767,7 +889,17 @@ def test_get_mapping_data_for_edition_first_suit_first_card(self) -> None: "17.1.1", ] }, - 215: {"owasp_asvs": ["2.4.1", "13.2.2", "13.4.2", "13.4.6", "16.2.5", "16.4.2", "16.5.1"]}, + 215: { + "owasp_asvs": [ + "2.4.1", + "13.2.2", + "13.4.2", + "13.4.6", + "16.2.5", + "16.4.2", + "16.5.1", + ] + }, 224: { "owasp_asvs": [ "4.3.2", @@ -782,16 +914,37 @@ def test_get_mapping_data_for_edition_first_suit_first_card(self) -> None: ] }, 497: {"owasp_asvs": ["13.2.2", "13.4.1", "13.4.3", "13.4.7", "15.2.3"]}, - 541: {"owasp_asvs": ["13.2.2", "13.4.2", "13.4.4", "13.4.5", "13.4.6", "13.4.7", "15.2.3"]}, + 541: { + "owasp_asvs": [ + "13.2.2", + "13.4.2", + "13.4.4", + "13.4.5", + "13.4.6", + "13.4.7", + "15.2.3", + ] + }, 546: {"owasp_asvs": ["15.2.3"]}, }, "safecode": [4, 23], "owasp_cre": { - "owasp_asvs": ["232-325", "774-888", "615-744", "067-050", "838-636", "253-452", "462-245", "743-110"] + "owasp_asvs": [ + "232-325", + "774-888", + "615-744", + "067-050", + "838-636", + "253-452", + "462-245", + "743-110", + ] }, } - got_suits = c.get_mapping_data_for_edition(self.input_yaml_files, self.input_language)["suits"] + got_suits = c.get_mapping_data_for_edition( + self.input_yaml_files, self.input_language + )["suits"] got_first_suit_keys = got_suits[0].keys() self.assertEqual(want_first_suit_keys, got_first_suit_keys) got_first_suit_first_card = got_suits[0]["cards"][0] @@ -800,12 +953,19 @@ def test_get_mapping_data_for_edition_first_suit_first_card(self) -> None: class TestGetLanguageDataFor1dot30(unittest.TestCase): def setUp(self) -> None: - test_source_yaml = os.path.join(c.convert_vars.BASE_PATH, "tests", "test_files", "source", "*.yaml") + test_source_yaml = os.path.join( + c.convert_vars.BASE_PATH, "tests", "test_files", "source", "*.yaml" + ) self.input_yaml_files = glob.glob(test_source_yaml) self.input_language = "en" self.input_version = "3.0" self.test_data: Dict[str, Any] = { - "meta": {"edition": "webapp", "component": "cards", "language": "EN", "version": "3.0"}, + "meta": { + "edition": "webapp", + "component": "cards", + "language": "EN", + "version": "3.0", + }, "suits": [ { "id": "VE", @@ -846,9 +1006,16 @@ def setUp(self) -> None: } def test_get_language_data_translation_meta(self) -> None: - want_meta = {"edition": "webapp", "component": "cards", "language": "EN", "version": "3.0"} + want_meta = { + "edition": "webapp", + "component": "cards", + "language": "EN", + "version": "3.0", + } - got_data = c.get_language_data(self.input_yaml_files, self.input_language, self.input_version) + got_data = c.get_language_data( + self.input_yaml_files, self.input_language, self.input_version + ) self.assertEqual(want_meta, got_data["meta"]) def test_get_language_data_translation_en_first_suit_first_card(self) -> None: @@ -856,13 +1023,19 @@ def test_get_language_data_translation_en_first_suit_first_card(self) -> None: want_first_suit_first_card_keys = self.test_data["suits"][0]["cards"][0].keys() want_first_suit_first_card_value = self.test_data["suits"][0]["cards"][0]["id"] - got_suits = c.get_language_data(self.input_yaml_files, self.input_language, self.input_version)["suits"] + got_suits = c.get_language_data( + self.input_yaml_files, self.input_language, self.input_version + )["suits"] got_first_suit_keys = got_suits[0].keys() self.assertEqual(want_first_suit_keys, got_first_suit_keys) got_first_suit_first_card_keys = got_suits[0]["cards"][0].keys() - self.assertEqual(want_first_suit_first_card_keys, got_first_suit_first_card_keys) + self.assertEqual( + want_first_suit_first_card_keys, got_first_suit_first_card_keys + ) got_first_suit_first_card_value = got_suits[0]["cards"][0]["id"] - self.assertEqual(want_first_suit_first_card_value, got_first_suit_first_card_value) + self.assertEqual( + want_first_suit_first_card_value, got_first_suit_first_card_value + ) def test_get_language_data_translation_es_first_suit_first_card(self) -> None: input_language = "es" @@ -870,13 +1043,19 @@ def test_get_language_data_translation_es_first_suit_first_card(self) -> None: want_first_suit_first_card_keys = self.test_data["suits"][0]["cards"][0].keys() want_first_suit_first_card_value = self.test_data["suits"][0]["cards"][0]["id"] - got_suits = c.get_language_data(self.input_yaml_files, input_language, self.input_version)["suits"] + got_suits = c.get_language_data( + self.input_yaml_files, input_language, self.input_version + )["suits"] got_first_suit_keys = got_suits[0].keys() self.assertEqual(want_first_suit_keys, got_first_suit_keys) got_first_suit_first_card_keys = got_suits[0]["cards"][0].keys() - self.assertEqual(want_first_suit_first_card_keys, got_first_suit_first_card_keys) + self.assertEqual( + want_first_suit_first_card_keys, got_first_suit_first_card_keys + ) got_first_suit_first_card_value = got_suits[0]["cards"][0]["id"] - self.assertEqual(want_first_suit_first_card_value, got_first_suit_first_card_value) + self.assertEqual( + want_first_suit_first_card_value, got_first_suit_first_card_value + ) def test_get_mapping_data_for_edition_asvs4(self) -> None: want_meta = { @@ -888,7 +1067,9 @@ def test_get_mapping_data_for_edition_asvs4(self) -> None: "templates": ["bridge_qr", "bridge", "tarot", "tarot_qr"], "languages": ["en", "es"], } - got_data = c.get_mapping_data_for_edition(self.input_yaml_files, self.input_language, self.input_version) + got_data = c.get_mapping_data_for_edition( + self.input_yaml_files, self.input_language, self.input_version + ) self.assertEqual(want_meta, got_data["meta"]) def test_get_mapping_data_for_edition_first_suit_first_card_asvs4(self) -> None: @@ -948,7 +1129,22 @@ def test_get_mapping_data_for_edition_first_suit_first_card_asvs4(self) -> None: "16.5.1", "17.1.1", ], - "capec": [54, 113, 116, 143, 144, 149, 150, 155, 169, 215, 224, 497, 541, 546], + "capec": [ + 54, + 113, + 116, + 143, + 144, + 149, + 150, + 155, + 169, + 215, + 224, + 497, + 541, + 546, + ], "capec_map": { 54: { "owasp_asvs": [ @@ -1023,7 +1219,17 @@ def test_get_mapping_data_for_edition_first_suit_first_card_asvs4(self) -> None: "17.1.1", ] }, - 215: {"owasp_asvs": ["2.4.1", "13.2.2", "13.4.2", "13.4.6", "16.2.5", "16.4.2", "16.5.1"]}, + 215: { + "owasp_asvs": [ + "2.4.1", + "13.2.2", + "13.4.2", + "13.4.6", + "16.2.5", + "16.4.2", + "16.5.1", + ] + }, 224: { "owasp_asvs": [ "4.3.2", @@ -1038,18 +1244,37 @@ def test_get_mapping_data_for_edition_first_suit_first_card_asvs4(self) -> None: ] }, 497: {"owasp_asvs": ["13.2.2", "13.4.1", "13.4.3", "13.4.7", "15.2.3"]}, - 541: {"owasp_asvs": ["13.2.2", "13.4.2", "13.4.4", "13.4.5", "13.4.6", "13.4.7", "15.2.3"]}, + 541: { + "owasp_asvs": [ + "13.2.2", + "13.4.2", + "13.4.4", + "13.4.5", + "13.4.6", + "13.4.7", + "15.2.3", + ] + }, 546: {"owasp_asvs": ["15.2.3"]}, }, "safecode": [4, 23], "owasp_cre": { - "owasp_asvs": ["232-325", "774-888", "615-744", "067-050", "838-636", "253-452", "462-245", "743-110"] + "owasp_asvs": [ + "232-325", + "774-888", + "615-744", + "067-050", + "838-636", + "253-452", + "462-245", + "743-110", + ] }, } - got_suits = c.get_mapping_data_for_edition(self.input_yaml_files, self.input_language, self.input_version)[ - "suits" - ] + got_suits = c.get_mapping_data_for_edition( + self.input_yaml_files, self.input_language, self.input_version + )["suits"] got_first_suit_keys = got_suits[0].keys() self.assertEqual(want_first_suit_keys, got_first_suit_keys) got_first_suit_first_card = got_suits[0]["cards"][0] @@ -1149,7 +1374,13 @@ def test_parse_arguments_no_args(self) -> None: class TestGetFilesFromOfType(unittest.TestCase): def test_get_files_from_of_type_source_yaml_files(self) -> None: c.convert_vars.args = argparse.Namespace(debug=False) - path = os.path.join(c.convert_vars.BASE_PATH, "tests", "test_files", "source", "convert_get_files_test") + path = os.path.join( + c.convert_vars.BASE_PATH, + "tests", + "test_files", + "source", + "convert_get_files_test", + ) ext = "yaml" want_files = list(path + os.sep + f for f in ["webapp-mappings-3.0.yaml"]) @@ -1159,9 +1390,13 @@ def test_get_files_from_of_type_source_yaml_files(self) -> None: def test_get_files_from_of_type_source_docx_files(self) -> None: c.convert_vars.args = argparse.Namespace(debug=False) - path = os.path.join(c.convert_vars.BASE_PATH, "tests", "test_files", "resources", "templates") + path = os.path.join( + c.convert_vars.BASE_PATH, "tests", "test_files", "resources", "templates" + ) ext = "docx" - want_files = [path + os.sep + "owasp_cornucopia_webapp_ver_guide_bridge_lang.docx"] + want_files = [ + path + os.sep + "owasp_cornucopia_webapp_ver_guide_bridge_lang.docx" + ] got_files = c.get_files_from_of_type(path, ext) self.assertEqual(len(want_files), len(got_files)) @@ -1174,7 +1409,8 @@ def test_get_files_from_of_type_source_empty_list(self) -> None: ext = "ext" want_files: typing.List[str] = [] want_logging_error_message = [ - "ERROR:root:No language files found in folder: " + str(os.path.join(c.convert_vars.BASE_PATH, "source")) + "ERROR:root:No language files found in folder: " + + str(os.path.join(c.convert_vars.BASE_PATH, "source")) ] with self.assertLogs(logging.getLogger(), logging.ERROR) as ll: @@ -1204,7 +1440,10 @@ def test_get_docx_document_success(self) -> None: def test_get_docx_document_failure(self) -> None: file = os.path.join( - c.convert_vars.BASE_PATH, "tests", "test_files", "owasp_cornucopia_webapp_ver_guide_bridge_lang.d" + c.convert_vars.BASE_PATH, + "tests", + "test_files", + "owasp_cornucopia_webapp_ver_guide_bridge_lang.d", ) want_type = type(docx.Document()) want_len_paragraphs = 0 @@ -1222,7 +1461,12 @@ class TestGetReplacementDict(unittest.TestCase): def setUp(self) -> None: c.convert_vars.args = argparse.Namespace(debug=False) self.input_data = { - "meta": {"edition": "webapp", "component": "cards", "language": "EN", "version": "3.0"}, + "meta": { + "edition": "webapp", + "component": "cards", + "language": "EN", + "version": "3.0", + }, "suits": [ { "id": "VE", @@ -1251,7 +1495,11 @@ def setUp(self) -> None: "desc": "You have invented a new attack against Authentication", "misc": "Read more about this topic in OWASP's free Cheat Sheet", }, - {"id": "AT2", "value": "2", "desc": "James can undertake authentication functions without"}, + { + "id": "AT2", + "value": "2", + "desc": "James can undertake authentication functions without", + }, ], }, ], @@ -1259,7 +1507,12 @@ def setUp(self) -> None: def test_build_template_dict_success(self) -> None: want_data = { - "meta": {"edition": "webapp", "component": "cards", "language": "EN", "version": "3.0"}, + "meta": { + "edition": "webapp", + "component": "cards", + "language": "EN", + "version": "3.0", + }, "${VE_suit}": "Data validation & encoding", "${VE_VEA_id}": "VEA", "${VE_VEA_value}": "A", @@ -1343,9 +1596,14 @@ def tearDown(self) -> None: def test_convert_docx_to_pdf_false(self) -> None: c.convert_vars.can_convert_to_pdf = False input_docx_filename = os.path.join( - c.convert_vars.BASE_PATH, "tests", "test_files", "owasp_cornucopia_webapp_ver_guide_bridge_lang.docx" + c.convert_vars.BASE_PATH, + "tests", + "test_files", + "owasp_cornucopia_webapp_ver_guide_bridge_lang.docx", + ) + want_pdf_filename = os.path.join( + c.convert_vars.BASE_PATH, "tests", "test_files", "test.pdf" ) - want_pdf_filename = os.path.join(c.convert_vars.BASE_PATH, "tests", "test_files", "test.pdf") # The message varies by platform - Windows/Mac get MS Word suggestion, Linux doesn't base_msg = ( f"WARNING:root:Error. A temporary file {input_docx_filename} was created in the output folder " @@ -1413,24 +1671,36 @@ def test_get_mapping_dict_true(self) -> None: "${VE_VE3_owasp_cre}": "{'owasp_asvs': ['848-711', '743-237', '042-550', '031-447', '532-878', '314-131', '036-725']}", # noqa: E501 } - got_mapping_dict = c.get_mapping_for_edition(input_yaml_files, "3.0", "en", "webapp", "bridge", "cards") + got_mapping_dict = c.get_mapping_for_edition( + input_yaml_files, "3.0", "en", "webapp", "bridge", "cards" + ) self.assertDictEqual(want_mapping_dict, got_mapping_dict) def test_get_mapping_for_edition_empty(self) -> None: - input_yaml_files = [os.path.join(self.BASE_PATH, "source", "webapp-cards-3.0-en.yaml")] + input_yaml_files = [ + os.path.join(self.BASE_PATH, "source", "webapp-cards-3.0-en.yaml") + ] want_mapping_dict: Dict[str, str] = {} with self.assertLogs(logging.getLogger(), logging.WARN) as ll: - got_mapping_dict = c.get_mapping_for_edition(input_yaml_files, "3.0", "en", "webapp", "bridge", "cards") + got_mapping_dict = c.get_mapping_for_edition( + input_yaml_files, "3.0", "en", "webapp", "bridge", "cards" + ) self.assertIn("WARNING:root:No mapping file found", " ".join(ll.output)) self.assertDictEqual(want_mapping_dict, got_mapping_dict) def test_get_mapping_for_edition_wrong_file_type(self) -> None: - input_yaml_files = [os.path.join(self.BASE_PATH, "resources", "originals", "owasp_cornucopia_en.docx")] + input_yaml_files = [ + os.path.join( + self.BASE_PATH, "resources", "originals", "owasp_cornucopia_en.docx" + ) + ] want_mapping_dict: Dict[str, str] = {} with self.assertLogs(logging.getLogger(), logging.WARN) as ll: - got_mapping_dict = c.get_mapping_for_edition(input_yaml_files, "3.0", "en", "webapp", "bridge", "cards") + got_mapping_dict = c.get_mapping_for_edition( + input_yaml_files, "3.0", "en", "webapp", "bridge", "cards" + ) self.assertIn("WARNING:root:No mapping file found", " ".join(ll.output)) self.assertDictEqual(want_mapping_dict, got_mapping_dict) @@ -1438,7 +1708,12 @@ def test_get_mapping_for_edition_wrong_file_type(self) -> None: class TestcreateEditionFromTemplate(unittest.TestCase): def setUp(self) -> None: c.convert_vars.args = argparse.Namespace( - inputfile="", outputfile="", language="en", debug=False, pdf=False, layout="leaflet" + inputfile="", + outputfile="", + language="en", + debug=False, + pdf=False, + layout="leaflet", ) self.b = c.convert_vars.BASE_PATH self.default_template_filename = c.convert_vars.DEFAULT_TEMPLATE_FILENAME @@ -1458,7 +1733,9 @@ def tearDown(self) -> None: os.remove(self.temp_file) def test_create_edition_from_template_none_valid_input(self) -> None: - self.want_file = os.path.join(c.convert_vars.BASE_PATH, "output", "owasp_cornucopia_webapp_invalid.docx") + self.want_file = os.path.join( + c.convert_vars.BASE_PATH, "output", "owasp_cornucopia_webapp_invalid.docx" + ) if os.path.isfile(self.want_file): os.remove(self.want_file) @@ -1471,18 +1748,24 @@ def test_create_edition_from_template_none_valid_input(self) -> None: ) def test_create_edition_from_template_wrong_base_path(self) -> None: - c.convert_vars.BASE_PATH = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0] + "invalidpath" + c.convert_vars.BASE_PATH = ( + os.path.split(os.path.dirname(os.path.realpath(__file__)))[0] + + "invalidpath" + ) if os.path.isfile(self.want_file): os.remove(self.want_file) with self.assertLogs(logging.getLogger(), logging.ERROR) as l2: c.create_edition_from_template("invalid", "invalid", "invalid", "invalid") self.assertIn( - "ERROR:root:No language files found in folder: " + str(os.path.join(c.convert_vars.BASE_PATH, "source")), + "ERROR:root:No language files found in folder: " + + str(os.path.join(c.convert_vars.BASE_PATH, "source")), " ".join(l2.output), ) - def test_create_edition_from_template_with_wrong_default_template_file_name(self) -> None: + def test_create_edition_from_template_with_wrong_default_template_file_name( + self, + ) -> None: c.convert_vars.DEFAULT_TEMPLATE_FILENAME = "does_not_exists" with self.assertLogs(logging.getLogger(), logging.DEBUG) as l2: @@ -1524,7 +1807,9 @@ def test_create_edition_from_template_with_pdf_option_on(self) -> None: " ".join(l2.output), ) - def test_create_edition_from_template_with_pdf_option_on_system_that_can_not_convert(self) -> None: + def test_create_edition_from_template_with_pdf_option_on_system_that_can_not_convert( + self, + ) -> None: c.convert_vars.can_convert_to_pdf = True c.convert_vars.args = argparse.Namespace( inputfile="", @@ -1567,20 +1852,28 @@ def test_create_edition_from_template_with_wrong_template(self) -> None: ) def test_create_edition_from_template_spanish(self) -> None: - want_file = os.path.join(c.convert_vars.BASE_PATH, "output", "owasp_cornucopia_webapp_3.0_guide_bridge_es.odt") + want_file = os.path.join( + c.convert_vars.BASE_PATH, + "output", + "owasp_cornucopia_webapp_3.0_guide_bridge_es.odt", + ) if os.path.isfile(self.want_file): os.remove(self.want_file) with self.assertLogs(logging.getLogger(), logging.INFO) as ll: c.create_edition_from_template("guide", "es") self.assertIn("INFO:root:New file saved:", " ".join(ll.output)) - self.assertIn("owasp_cornucopia_webapp_3.0_guide_bridge_es.odt", " ".join(ll.output)) + self.assertIn( + "owasp_cornucopia_webapp_3.0_guide_bridge_es.odt", " ".join(ll.output) + ) self.assertTrue(os.path.isfile(want_file)) def test_create_edition_from_template_en_idml(self) -> None: self.want_file = os.path.join( - c.convert_vars.BASE_PATH, "output", "owasp_cornucopia_webapp_3.0_cards_bridge_en.idml" + c.convert_vars.BASE_PATH, + "output", + "owasp_cornucopia_webapp_3.0_cards_bridge_en.idml", ) c.convert_vars.args.outputfile = self.want_file if os.path.isfile(self.want_file): @@ -1589,12 +1882,18 @@ def test_create_edition_from_template_en_idml(self) -> None: with self.assertLogs(logging.getLogger(), logging.INFO) as ll: c.create_edition_from_template("cards", "en") self.assertIn("INFO:root:New file saved:", " ".join(ll.output)) - self.assertIn("owasp_cornucopia_webapp_3.0_cards_bridge_en.idml", " ".join(ll.output)) + self.assertIn( + "owasp_cornucopia_webapp_3.0_cards_bridge_en.idml", " ".join(ll.output) + ) self.assertTrue(os.path.isfile(self.want_file)) def test_create_edition_from_template_es_specify_output(self) -> None: - c.convert_vars.args.outputfile = os.path.join("output", "cornucopia_cards_es.idml") - self.want_file = os.path.join(c.convert_vars.BASE_PATH, c.convert_vars.args.outputfile) + c.convert_vars.args.outputfile = os.path.join( + "output", "cornucopia_cards_es.idml" + ) + self.want_file = os.path.join( + c.convert_vars.BASE_PATH, c.convert_vars.args.outputfile + ) if os.path.isfile(self.want_file): os.remove(self.want_file) @@ -1643,7 +1942,9 @@ def test_save_idml_file_test_location(self) -> None: "owasp_cornucopia_webapp_ver_cards_bridge_lang.idml", ) self.want_file = os.path.join( - c.convert_vars.BASE_PATH, "output", "owasp_cornucopia_webapp_3.0_cards_bridge_en.idml" + c.convert_vars.BASE_PATH, + "output", + "owasp_cornucopia_webapp_3.0_cards_bridge_en.idml", ) c.save_idml_file(input_template_doc, self.language_dict, self.want_file) @@ -1670,9 +1971,16 @@ def tearDown(self) -> None: os.remove(self.want_file) def test_save_docx_file_defaults(self) -> None: - filename = os.path.join(c.convert_vars.BASE_PATH, "resources", "originals", "owasp_cornucopia_en_static.docx") + filename = os.path.join( + c.convert_vars.BASE_PATH, + "resources", + "originals", + "owasp_cornucopia_en_static.docx", + ) input_doc = docx.Document(filename) - self.want_file = os.path.join(c.convert_vars.BASE_PATH, "output", "owasp_cornucopia_en_static.docx") + self.want_file = os.path.join( + c.convert_vars.BASE_PATH, "output", "owasp_cornucopia_en_static.docx" + ) if os.path.isfile(self.want_file): os.remove(self.want_file) @@ -1698,7 +2006,9 @@ def setUp(self) -> None: } self.b = c.convert_vars.BASE_PATH c.convert_vars.BASE_PATH = os.path.join(self.b, "tests", "test_files") - self.input_xml_file = os.path.join(c.convert_vars.BASE_PATH, "output", "temp", "Stories", "Story_u8fb5.xml") + self.input_xml_file = os.path.join( + c.convert_vars.BASE_PATH, "output", "temp", "Stories", "Story_u8fb5.xml" + ) if not os.path.exists(os.path.dirname(self.input_xml_file)): os.makedirs(os.path.dirname(self.input_xml_file)) if os.path.isfile(self.input_xml_file): @@ -1748,7 +2058,9 @@ def setUp(self) -> None: } self.b = c.convert_vars.BASE_PATH c.convert_vars.BASE_PATH = os.path.join(self.b, "tests", "test_files") - self.input_xml_file = os.path.join(c.convert_vars.BASE_PATH, "output", "temp", "Stories", "Story_u8fb5.xml") + self.input_xml_file = os.path.join( + c.convert_vars.BASE_PATH, "output", "temp", "Stories", "Story_u8fb5.xml" + ) if not os.path.exists(os.path.dirname(self.input_xml_file)): os.makedirs(os.path.dirname(self.input_xml_file)) if os.path.isfile(self.input_xml_file): @@ -1790,7 +2102,9 @@ def setUp(self) -> None: } self.b = c.convert_vars.BASE_PATH c.convert_vars.BASE_PATH = os.path.join(self.b, "tests", "test_files") - self.input_xml_file = os.path.join(c.convert_vars.BASE_PATH, "output", "temp", "Stories", "Story_u8fb5.xml") + self.input_xml_file = os.path.join( + c.convert_vars.BASE_PATH, "output", "temp", "Stories", "Story_u8fb5.xml" + ) if not os.path.exists(os.path.dirname(self.input_xml_file)): os.makedirs(os.path.dirname(self.input_xml_file)) if os.path.isfile(self.input_xml_file): @@ -1826,7 +2140,9 @@ def setUp(self) -> None: self.input_dict = {} self.b = c.convert_vars.BASE_PATH c.convert_vars.BASE_PATH = os.path.join(self.b, "tests", "test_files") - self.input_xml_file = os.path.join(c.convert_vars.BASE_PATH, "output", "temp", "Stories", "Story_u8fb5.xml") + self.input_xml_file = os.path.join( + c.convert_vars.BASE_PATH, "output", "temp", "Stories", "Story_u8fb5.xml" + ) if not os.path.exists(os.path.dirname(self.input_xml_file)): os.makedirs(os.path.dirname(self.input_xml_file)) if os.path.isfile(self.input_xml_file): @@ -1847,8 +2163,14 @@ def test_replace_text_in_xml_file_fail(self) -> None: """ with self.assertLogs(logging.getLogger(), logging.ERROR) as ll: - c.replace_text_in_xml_file(self.input_xml_file, list(self.input_dict.items())) - self.assertIn("ERROR:root:Failed to parse XML file", ll.output.pop(), "No xml parsing error was caught.") + c.replace_text_in_xml_file( + self.input_xml_file, list(self.input_dict.items()) + ) + self.assertIn( + "ERROR:root:Failed to parse XML file", + ll.output.pop(), + "No xml parsing error was caught.", + ) with open(self.input_xml_file, "r", encoding="utf-8") as f: got_data = f.read() @@ -1859,43 +2181,90 @@ class Test1(unittest.TestCase): def setUp(self) -> None: self.replacement_values = [ ("${VE_suit}", "Validation & Encoding"), - ("${VE_VE2_desc}", "You have invented a new attack against Data Validation and Encoding"), + ( + "${VE_VE2_desc}", + "You have invented a new attack against Data Validation and Encoding", + ), ("${WC_suit}", "Wild Card"), - ("${WC_JOA_desc}", "Alice can utilize the application to attack users' systems and data"), + ( + "${WC_JOA_desc}", + "Alice can utilize the application to attack users' systems and data", + ), ] def test_get_replacement_value_from_dict_exact(self) -> None: input_text = "${VE_VE2_desc}" - want_data = "You have invented a new attack against Data Validation and Encoding" + want_data = ( + "You have invented a new attack against Data Validation and Encoding" + ) - got_data = c.get_replacement_value_from_dict(input_text, self.replacement_values) + got_data = c.get_replacement_value_from_dict( + input_text, self.replacement_values + ) self.assertEqual(want_data, got_data) def test_get_replacement_value_from_dict_spaced(self) -> None: input_text = " ${VE_VE2_desc} " - want_data = " You have invented a new attack against Data Validation and Encoding " + want_data = ( + " You have invented a new attack against Data Validation and Encoding " + ) - got_data = c.get_replacement_value_from_dict(input_text, self.replacement_values) + got_data = c.get_replacement_value_from_dict( + input_text, self.replacement_values + ) self.assertEqual(want_data, got_data) class TestCheckMakeListIntoText(unittest.TestCase): def test_check_make_list_into_text_success(self) -> None: - input_list = ["69", "107", "108", "109", "136", "137", "153", "156", "158", "162"] + input_list = [ + "69", + "107", + "108", + "109", + "136", + "137", + "153", + "156", + "158", + "162", + ] want_text = "69, 107-109, 136-137, 153, 156, 158, 162" got_text = c.check_make_list_into_text(input_list) self.assertEqual(want_text, got_text) def test_check_make_list_into_text_not_grouped(self) -> None: - input_list = ["69", "107", "108", "109", "136", "137", "153", "156", "158", "162"] + input_list = [ + "69", + "107", + "108", + "109", + "136", + "137", + "153", + "156", + "158", + "162", + ] want_text = "69, 107-109, 136-137, 153, 156, 158, 162" got_text = c.check_make_list_into_text(input_list) self.assertEqual(want_text, got_text) def test_check_make_list_into_text_not_numeric(self) -> None: - input_list = ["69", "107", "108", "109", "Ess", "137", "153", "156", "158", "162"] + input_list = [ + "69", + "107", + "108", + "109", + "Ess", + "137", + "153", + "156", + "158", + "162", + ] want_text = "69, 107, 108, 109, Ess, 137, 153, 156, 158, 162" got_text = c.check_make_list_into_text(input_list) @@ -1904,15 +2273,48 @@ def test_check_make_list_into_text_not_numeric(self) -> None: class TestGroupNumberRanges(unittest.TestCase): def test_group_number_ranges_success(self) -> None: - input_list = ["69", "107", "108", "109", "136", "137", "153", "156", "158", "162"] + input_list = [ + "69", + "107", + "108", + "109", + "136", + "137", + "153", + "156", + "158", + "162", + ] want_text = ["69", "107-109", "136-137", "153", "156", "158", "162"] got_text = c.group_number_ranges(input_list) self.assertEqual(want_text, got_text) def test_group_number_ranges_non_numeric(self) -> None: - input_list = ["69", "East", "West", "109", "136", "137", "153", "156", "158", "162"] - want_text = ["69", "East", "West", "109", "136", "137", "153", "156", "158", "162"] + input_list = [ + "69", + "East", + "West", + "109", + "136", + "137", + "153", + "156", + "158", + "162", + ] + want_text = [ + "69", + "East", + "West", + "109", + "136", + "137", + "153", + "156", + "158", + "162", + ] got_text = c.group_number_ranges(input_list) self.assertEqual(want_text, got_text) @@ -1980,7 +2382,9 @@ class TestZipDir(unittest.TestCase): def setUp(self) -> None: self.b = c.convert_vars.BASE_PATH c.convert_vars.BASE_PATH = os.path.join(self.b, "tests", "test_files") - self.input_filename = os.path.join(c.convert_vars.BASE_PATH, "output", "test.zip") + self.input_filename = os.path.join( + c.convert_vars.BASE_PATH, "output", "test.zip" + ) def tearDown(self) -> None: if os.path.isfile(self.input_filename): @@ -2047,7 +2451,10 @@ def get_docx_text(doc: docx.Document) -> List[str]: def test_replace_docx_inline_text_expected_keys_present(self) -> None: template_docx_file = os.path.join( - c.convert_vars.BASE_PATH, "resources", "templates", "owasp_cornucopia_webapp_ver_guide_bridge_lang.docx" + c.convert_vars.BASE_PATH, + "resources", + "templates", + "owasp_cornucopia_webapp_ver_guide_bridge_lang.docx", ) doc = docx.Document(template_docx_file) input_replacement_data = { @@ -2061,7 +2468,10 @@ def test_replace_docx_inline_text_expected_keys_present(self) -> None: def test_replace_docx_inline_text_new_text_present(self) -> None: template_docx_file = os.path.join( - c.convert_vars.BASE_PATH, "resources", "templates", "owasp_cornucopia_webapp_ver_guide_bridge_lang.docx" + c.convert_vars.BASE_PATH, + "resources", + "templates", + "owasp_cornucopia_webapp_ver_guide_bridge_lang.docx", ) doc = docx.Document(template_docx_file) input_replacement_data = { @@ -2076,7 +2486,10 @@ def test_replace_docx_inline_text_new_text_present(self) -> None: def test_replace_docx_inline_text_keys_replaced(self) -> None: template_docx_file = os.path.join( - c.convert_vars.BASE_PATH, "resources", "templates", "owasp_cornucopia_webapp_ver_guide_bridge_lang.docx" + c.convert_vars.BASE_PATH, + "resources", + "templates", + "owasp_cornucopia_webapp_ver_guide_bridge_lang.docx", ) doc = docx.Document(template_docx_file) input_replacement_data = { @@ -2100,7 +2513,10 @@ def tearDown(self) -> None: def test_get_document_paragraphs_len_paragraphs(self) -> None: template_docx_file = os.path.join( - c.convert_vars.BASE_PATH, "resources", "templates", "owasp_cornucopia_webapp_ver_guide_bridge_lang.docx" + c.convert_vars.BASE_PATH, + "resources", + "templates", + "owasp_cornucopia_webapp_ver_guide_bridge_lang.docx", ) doc = docx.Document(template_docx_file) # Accept a range to handle platform/version differences in docx parsing @@ -2110,15 +2526,22 @@ def test_get_document_paragraphs_len_paragraphs(self) -> None: paragraphs = c.get_document_paragraphs(doc) self.assertGreaterEqual( - len(paragraphs), min_paragraphs, f"Expected at least {min_paragraphs} paragraphs, got {len(paragraphs)}" + len(paragraphs), + min_paragraphs, + f"Expected at least {min_paragraphs} paragraphs, got {len(paragraphs)}", ) self.assertLessEqual( - len(paragraphs), max_paragraphs, f"Expected at most {max_paragraphs} paragraphs, got {len(paragraphs)}" + len(paragraphs), + max_paragraphs, + f"Expected at most {max_paragraphs} paragraphs, got {len(paragraphs)}", ) def test_get_document_paragraphs_find_text(self) -> None: template_docx_file = os.path.join( - c.convert_vars.BASE_PATH, "resources", "templates", "owasp_cornucopia_webapp_ver_guide_bridge_lang.docx" + c.convert_vars.BASE_PATH, + "resources", + "templates", + "owasp_cornucopia_webapp_ver_guide_bridge_lang.docx", ) doc = docx.Document(template_docx_file) want_text_list = [ @@ -2147,7 +2570,10 @@ def tearDown(self) -> None: def test_get_paragraphs_from_table_in_doc(self) -> None: template_docx_file = os.path.join( - c.convert_vars.BASE_PATH, "resources", "templates", "owasp_cornucopia_webapp_ver_guide_bridge_lang.docx" + c.convert_vars.BASE_PATH, + "resources", + "templates", + "owasp_cornucopia_webapp_ver_guide_bridge_lang.docx", ) doc = docx.Document(template_docx_file) doc_tables = doc.tables diff --git a/tests/scripts/smoke_tests.py b/tests/scripts/smoke_tests.py index 8d7a286fc..dcd618245 100644 --- a/tests/scripts/smoke_tests.py +++ b/tests/scripts/smoke_tests.py @@ -38,21 +38,33 @@ def _make_request(self, url: str, timeout: int = 30) -> requests.Response: def test_01_homepage_loads(self) -> None: """Test that the Copi homepage loads successfully""" response = self._make_request(self.BASE_URL) - self.assertEqual(response.status_code, 200, f"Homepage returned status {response.status_code}") - self.assertIn("copi", response.text.lower(), "Homepage should contain 'copi' text") + self.assertEqual( + response.status_code, + 200, + f"Homepage returned status {response.status_code}", + ) + self.assertIn( + "copi", response.text.lower(), "Homepage should contain 'copi' text" + ) def test_02_cards_route_accessible(self) -> None: """Test that the cards route is accessible""" url = urljoin(self.BASE_URL, "/cards") response = self._make_request(url) - self.assertEqual(response.status_code, 200, f"Cards route returned status {response.status_code}") + self.assertEqual( + response.status_code, + 200, + f"Cards route returned status {response.status_code}", + ) def test_03_javascript_loads(self) -> None: """Test that JavaScript assets are being served""" response = self._make_request(self.BASE_URL) self.assertEqual(response.status_code, 200) self.assertTrue( - " str: return f"{opencre_rest_url}/id/{cre_id}" -def produce_webapp_mappings(source_file: Dict[Any, Any], standards_to_add: list[str]) -> Dict[Any, Any]: +def produce_webapp_mappings( + source_file: Dict[Any, Any], standards_to_add: list[str] +) -> Dict[Any, Any]: base = { - "meta": {"edition": "webapp", "component": "mappings", "language": "ALL", "version": CORNUCOPIA_VERSION}, + "meta": { + "edition": "webapp", + "component": "mappings", + "language": "ALL", + "version": CORNUCOPIA_VERSION, + }, } for indx, suit in enumerate(source_file.copy()["suits"]): for card_indx, card in enumerate(suit["cards"]): @@ -35,8 +42,8 @@ def produce_webapp_mappings(source_file: Dict[Any, Any], standards_to_add: list[ for standard in standards_to_add: for link in cre_object.get("links"): if link.get("document").get("name") == standard: - source_file["suits"][indx]["cards"][card_indx][standard] = link.get("document").get( - "sectionID" + source_file["suits"][indx]["cards"][card_indx][standard] = ( + link.get("document").get("sectionID") ) else: print(f"could not find CRE {cre}, status code {response.status_code}") @@ -59,13 +66,25 @@ def generate_qr_images(existing_mappings: Dict[Any, Any], directory_path: str) - def main() -> None: global opencre_base_url, opencre_rest_url parser = argparse.ArgumentParser(description="generate mappings") - parser.add_argument("-c", "--cres", help="Where to find the file mapping cornucopia to CREs", required=True) - parser.add_argument("-t", "--target", help="Path where to store the result", required=True) parser.add_argument( - "-s", "--staging", action="store_true", help="If provided will use staging.opencre.org instead of opencre.org" + "-c", + "--cres", + help="Where to find the file mapping cornucopia to CREs", + required=True, ) parser.add_argument( - "-q", "--qr_images", help="If provided will populate the target dir with qr image pointing to every cre" + "-t", "--target", help="Path where to store the result", required=True + ) + parser.add_argument( + "-s", + "--staging", + action="store_true", + help="If provided will use staging.opencre.org instead of opencre.org", + ) + parser.add_argument( + "-q", + "--qr_images", + help="If provided will populate the target dir with qr image pointing to every cre", ) args = vars(parser.parse_args()) if args["staging"]: From 04a2dda2d39ac99a0eaec492acb83edad05a1359 Mon Sep 17 00:00:00 2001 From: Kavya Seth Date: Fri, 13 Mar 2026 09:47:53 +0530 Subject: [PATCH 3/3] fix: apply black formatting --- scripts/capec_map_enricher.py | 26 +- scripts/check_translations.py | 54 ++-- scripts/check_translations_itest.py | 12 +- scripts/check_translations_utest.py | 46 +--- scripts/convert.py | 202 ++++---------- scripts/convert_asvs.py | 58 ++-- scripts/convert_capec.py | 74 ++--- scripts/convert_capec_map_to_asvs_map.py | 58 ++-- tests/scripts/capec_map_enricher_fuzzer.py | 17 +- tests/scripts/capec_map_enricher_itest.py | 36 +-- tests/scripts/capec_map_enricher_utest.py | 26 +- tests/scripts/convert_asvs_itest.py | 62 ++--- tests/scripts/convert_asvs_utest.py | 39 +-- tests/scripts/convert_capec_itest.py | 78 ++---- .../convert_capec_map_to_asvs_map_itest.py | 72 ++--- .../convert_capec_map_to_asvs_map_utest.py | 114 ++------ tests/scripts/convert_capec_utest.py | 44 +-- tests/scripts/convert_fuzzer.py | 32 +-- tests/scripts/convert_itest.py | 4 +- tests/scripts/convert_utest.py | 253 +++++------------- tests/scripts/smoke_tests.py | 8 +- tests/test_files/gen_mappings.py | 12 +- 22 files changed, 341 insertions(+), 986 deletions(-) diff --git a/scripts/capec_map_enricher.py b/scripts/capec_map_enricher.py index 0c073eee7..2a0c222c4 100644 --- a/scripts/capec_map_enricher.py +++ b/scripts/capec_map_enricher.py @@ -20,9 +20,7 @@ class EnricherVars: TEMPLATE_FILE_NAME: str = "EDITION-capec-VERSION.yaml" - DEFAULT_CAPEC_JSON_PATH = ( - Path(__file__).parent / "../cornucopia.owasp.org/data/capec-3.9/3000.json" - ) + DEFAULT_CAPEC_JSON_PATH = Path(__file__).parent / "../cornucopia.owasp.org/data/capec-3.9/3000.json" DEFAULT_SOURCE_DIR = Path(__file__).parent / "../source" args: argparse.Namespace @@ -75,9 +73,7 @@ def extract_capec_names(json_data: dict[str, Any]) -> dict[int, str]: return capec_names -def enrich_capec_mappings( - capec_mappings: dict[str, Any], capec_names: dict[int, str] -) -> dict[str, Any]: +def enrich_capec_mappings(capec_mappings: dict[str, Any], capec_names: dict[int, str]) -> dict[str, Any]: """ Enrich CAPEC mappings with names from the CAPEC catalog. @@ -111,9 +107,7 @@ def enrich_capec_mappings( enriched[capec_id_key] = enriched_entry - logging.info( - "Enriched %d CAPEC mappings", len(enriched) - (1 if "meta" in enriched else 0) - ) + logging.info("Enriched %d CAPEC mappings", len(enriched) - (1 if "meta" in enriched else 0)) return enriched @@ -161,9 +155,7 @@ def save_yaml_file(filepath: Path, data: dict[str, Any]) -> bool: """Save data as YAML file.""" try: with open(filepath, "w", encoding="utf-8") as f: - yaml.dump( - data, f, default_flow_style=False, sort_keys=False, allow_unicode=True - ) + yaml.dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True) logging.info("Successfully saved YAML file: %s", filepath) return True except Exception as e: @@ -185,9 +177,7 @@ def set_logging() -> None: def parse_arguments(input_args: list[str]) -> argparse.Namespace: """Parse command line arguments.""" - parser = argparse.ArgumentParser( - description="Enrich CAPEC mappings with names from CAPEC JSON catalog" - ) + parser = argparse.ArgumentParser(description="Enrich CAPEC mappings with names from CAPEC JSON catalog") parser.add_argument( "-c", "--capec-json", @@ -259,9 +249,9 @@ def main() -> None: if enricher_vars.args.input_path: input_path = Path(enricher_vars.args.input_path).resolve() else: - filename = EnricherVars.TEMPLATE_FILE_NAME.replace( - "EDITION", enricher_vars.args.edition - ).replace("VERSION", enricher_vars.args.version) + filename = EnricherVars.TEMPLATE_FILE_NAME.replace("EDITION", enricher_vars.args.edition).replace( + "VERSION", enricher_vars.args.version + ) input_path = directory / filename # Determine output file path diff --git a/scripts/check_translations.py b/scripts/check_translations.py index 489d8d804..1cffbad58 100644 --- a/scripts/check_translations.py +++ b/scripts/check_translations.py @@ -69,9 +69,7 @@ def get_file_groups(self) -> Dict[str, List[Path]]: return file_groups - def _separate_english_and_translations( - self, files: List[Path] - ) -> Tuple[Path | None, List[Path]]: + def _separate_english_and_translations(self, files: List[Path]) -> Tuple[Path | None, List[Path]]: """Separate English reference file from translation files.""" english_file = None translation_files = [] @@ -85,9 +83,7 @@ def _separate_english_and_translations( return english_file, translation_files - def _check_translation_tags( - self, english_tags: Dict[str, str], trans_tags: Dict[str, str] - ) -> Dict[str, Any]: + def _check_translation_tags(self, english_tags: Dict[str, str], trans_tags: Dict[str, str]) -> Dict[str, Any]: """Check translation tags against English reference.""" missing = [] untranslated = [] @@ -126,14 +122,10 @@ def check_translations(self) -> Dict[str, Dict[str, Any]]: file_groups = self.get_file_groups() for base_name, files in file_groups.items(): - english_file, translation_files = self._separate_english_and_translations( - files - ) + english_file, translation_files = self._separate_english_and_translations(files) if not english_file: - print( - f"Warning: No English file found for {base_name}", file=sys.stderr - ) + print(f"Warning: No English file found for {base_name}", file=sys.stderr) continue english_tags = self.extract_tags(english_file) @@ -161,9 +153,7 @@ def generate_markdown_report(self) -> str: return "\n".join(report_lines) report_lines.append("# Translation Check Report\n") - report_lines.append( - "The following sentences/tags have issues in the translations:\n" - ) + report_lines.append("The following sentences/tags have issues in the translations:\n") # Language name mapping lang_names = { @@ -199,9 +189,7 @@ def generate_markdown_report(self) -> str: if issues["untranslated"]: report_lines.append("### Untranslated Tags\n") - report_lines.append( - "The following tags have identical text to English (not translated):\n" - ) + report_lines.append("The following tags have identical text to English (not translated):\n") tags_str = ", ".join(issues["untranslated"]) report_lines.append(f"{tags_str}\n") @@ -226,11 +214,31 @@ def main() -> None: sys.exit(1) # Run checker - checker = TranslationChecker(source_dir, - excluded_tags=["T02330", "T02530", - "T03130", "T03150", "T03170", "T03190", "T03240", "T03260", - "T03350", "T03420", "T03470", "T03490", "T03540", "T03580", - "T03710", "T03730", "T03750", "T03770", "T03772", "T03774"]) + checker = TranslationChecker( + source_dir, + excluded_tags=[ + "T02330", + "T02530", + "T03130", + "T03150", + "T03170", + "T03190", + "T03240", + "T03260", + "T03350", + "T03420", + "T03470", + "T03490", + "T03540", + "T03580", + "T03710", + "T03730", + "T03750", + "T03770", + "T03772", + "T03774", + ], + ) results = checker.check_translations() # Generate report diff --git a/scripts/check_translations_itest.py b/scripts/check_translations_itest.py index 72dbadd75..201c9a6e5 100644 --- a/scripts/check_translations_itest.py +++ b/scripts/check_translations_itest.py @@ -29,16 +29,12 @@ def setUp(self) -> None: def test_source_directory_exists(self) -> None: """Test that the source directory exists.""" - self.assertTrue( - self.source_dir.exists(), f"Source directory not found: {self.source_dir}" - ) + self.assertTrue(self.source_dir.exists(), f"Source directory not found: {self.source_dir}") def test_english_files_exist(self) -> None: """Test that English card files exist.""" english_files = list(self.source_dir.glob("*-cards-*-en.yaml")) - self.assertGreater( - len(english_files), 0, "No English card files found in source directory" - ) + self.assertGreater(len(english_files), 0, "No English card files found in source directory") def test_translations_completeness(self) -> None: """ @@ -63,9 +59,7 @@ def test_translations_completeness(self) -> None: total_issues += len(issues.get("untranslated", [])) total_issues += len(issues.get("empty", [])) - self.fail( - f"\\n\\nTranslation issues found ({total_issues} total):\\n\\n{report}\\n" - ) + self.fail(f"\\n\\nTranslation issues found ({total_issues} total):\\n\\n{report}\\n") def test_no_duplicate_tags_in_english(self) -> None: """Test that English files don't have duplicate T0xxx tags.""" diff --git a/scripts/check_translations_utest.py b/scripts/check_translations_utest.py index 3271abe91..89cb6575c 100644 --- a/scripts/check_translations_utest.py +++ b/scripts/check_translations_utest.py @@ -86,9 +86,7 @@ def test_tag_format_validation(self) -> None: tags = self.checker.extract_tags(english_file) for tag_id in tags.keys(): - self.assertIsNotNone( - tag_pattern.match(tag_id), f"Tag {tag_id} doesn't match format T0xxxx" - ) + self.assertIsNotNone(tag_pattern.match(tag_id), f"Tag {tag_id} doesn't match format T0xxxx") def test_no_duplicate_tags(self) -> None: """Test that files don't have duplicate T0xxx tags.""" @@ -145,9 +143,7 @@ def test_tabs_and_newlines_detected_as_empty(self) -> None: result = self.checker._check_translation_tags(english_tags, trans_tags) - self.assertIn( - "T00002", result["empty"], "Tabs and newlines should be detected as empty" - ) + self.assertIn("T00002", result["empty"], "Tabs and newlines should be detected as empty") def test_extra_spaces_detected_as_untranslated(self) -> None: english_tags = {"T00001": "Login", "T00002": "Register"} @@ -171,9 +167,7 @@ def test_properly_translated_no_issues(self) -> None: result = self.checker._check_translation_tags(english_tags, trans_tags) self.assertEqual(len(result["missing"]), 0, "No missing tags expected") - self.assertEqual( - len(result["untranslated"]), 0, "No untranslated tags expected" - ) + self.assertEqual(len(result["untranslated"]), 0, "No untranslated tags expected") self.assertEqual(len(result["empty"]), 0, "No empty tags expected") @@ -238,12 +232,8 @@ def test_norwegian_translation_issues_detected(self) -> None: "Norwegian (no_nb) results should be present", ) no_nb_issues = results["test-cards-1.0"]["no_nb"] - self.assertIn( - "T00004", no_nb_issues["missing"], "T00004 should be missing in no_nb" - ) - self.assertIn( - "T00003", no_nb_issues["empty"], "T00003 should be empty in no_nb" - ) + self.assertIn("T00004", no_nb_issues["missing"], "T00004 should be missing in no_nb") + self.assertIn("T00003", no_nb_issues["empty"], "T00003 should be empty in no_nb") def test_portuguese_brazil_untranslated_detected(self) -> None: """Test that untranslated tags in Portuguese Brazil (pt_br) are detected.""" @@ -267,18 +257,14 @@ def test_portuguese_portugal_fully_translated_no_issues(self) -> None: results = self.checker.check_translations() pt_pt_issues = results.get("test-cards-1.0", {}).get("pt_pt", None) - self.assertIsNone( - pt_pt_issues, "pt_pt has no issues and should not appear in results" - ) + self.assertIsNone(pt_pt_issues, "pt_pt has no issues and should not appear in results") def test_lang_names_in_report(self) -> None: """Test that generate_markdown_report resolves compound locale display names correctly.""" self.checker.check_translations() report = self.checker.generate_markdown_report() - self.assertIn( - "Norwegian", report, "Norwegian display name should appear for no_nb" - ) + self.assertIn("Norwegian", report, "Norwegian display name should appear for no_nb") self.assertIn( "Portuguese (Brazil)", report, @@ -309,9 +295,7 @@ def test_get_file_groups_skips_archive_files(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: tmp_path = Path(tmpdir) # Place a YAML file whose name contains "archive" directly in source dir - (tmp_path / "webapp-archive-cards-1.0-en.yaml").write_text( - "---\nparagraphs: []" - ) + (tmp_path / "webapp-archive-cards-1.0-en.yaml").write_text("---\nparagraphs: []") (tmp_path / "test-cards-1.0-en.yaml").write_text("---\nparagraphs: []") checker = TranslationChecker(tmp_path) file_groups = checker.get_file_groups() @@ -350,9 +334,7 @@ def test_check_translations_empty_english_tags(self) -> None: (tmp_path / "test-cards-1.0-es.yaml").write_text("---\nparagraphs: []") checker = TranslationChecker(tmp_path) results = checker.check_translations() - self.assertEqual( - results, {}, "Should produce no results when English has no tags" - ) + self.assertEqual(results, {}, "Should produce no results when English has no tags") def test_main_exits_when_source_dir_missing(self) -> None: """Test that main() exits with code 1 when source directory does not exist.""" @@ -373,13 +355,9 @@ def test_main_runs_and_writes_report(self) -> None: mock_checker.check_translations.return_value = {} mock_checker.generate_markdown_report.return_value = "# Report\n\u2705 Done" - with patch.object( - check_translations, "TranslationChecker", return_value=mock_checker - ), patch("builtins.open", mock_open()), patch( - "builtins.print" - ), self.assertRaises( - SystemExit - ) as cm: + with patch.object(check_translations, "TranslationChecker", return_value=mock_checker), patch( + "builtins.open", mock_open() + ), patch("builtins.print"), self.assertRaises(SystemExit) as cm: check_translations.main() self.assertEqual(cm.exception.code, 0) diff --git a/scripts/convert.py b/scripts/convert.py index 17088ec12..767632107 100644 --- a/scripts/convert.py +++ b/scripts/convert.py @@ -53,9 +53,7 @@ class ConvertVars: "owasp_cornucopia_edition_ver_layout_document_template_lang", ] ) - DEFAULT_OUTPUT_FILENAME: str = os.sep.join( - ["output", "owasp_cornucopia_edition_ver_layout_document_template_lang"] - ) + DEFAULT_OUTPUT_FILENAME: str = os.sep.join(["output", "owasp_cornucopia_edition_ver_layout_document_template_lang"]) args: argparse.Namespace can_convert_to_pdf: bool = False @@ -82,9 +80,7 @@ def check_make_list_into_text(var: List[str]) -> str: return text_output -def _validate_file_paths( - source_filename: str, output_pdf_filename: str -) -> Tuple[bool, str, str]: +def _validate_file_paths(source_filename: str, output_pdf_filename: str) -> Tuple[bool, str, str]: """Validate and sanitize file paths to prevent command injection.""" source_path = os.path.abspath(source_filename) output_dir = os.path.abspath(os.path.dirname(output_pdf_filename)) @@ -126,9 +122,7 @@ def _safe_extractall(archive: zipfile.ZipFile, target_dir: str) -> None: # Block any member whose resolved path escapes the target directory. # The os.sep suffix prevents prefix collisions (e.g. /tmp/d vs /tmp/d_evil). if not member_path.startswith(abs_target + os.sep): - raise ValueError( - f"Zip Slip blocked: member '{member.filename}' would extract outside target directory" - ) + raise ValueError(f"Zip Slip blocked: member '{member.filename}' would extract outside target directory") archive.extract(member, target_dir) @@ -174,17 +168,13 @@ def _convert_with_libreoffice(source_filename: str, output_pdf_filename: str) -> logging.info(f"Using LibreOffice for conversion: {libreoffice_bin}") # Validate file paths - is_valid, source_path, output_dir = _validate_file_paths( - source_filename, output_pdf_filename - ) + is_valid, source_path, output_dir = _validate_file_paths(source_filename, output_pdf_filename) if not is_valid: logging.warning(source_path) # source_path contains the error message return False # Create user profile directory safely - user_profile_dir = os.path.abspath( - os.path.join(convert_vars.BASE_PATH, "output", "lo_profile") - ) + user_profile_dir = os.path.abspath(os.path.join(convert_vars.BASE_PATH, "output", "lo_profile")) os.makedirs(user_profile_dir, exist_ok=True) user_profile_url = "file:///" + user_profile_dir.replace("\\", "/") @@ -301,15 +291,11 @@ def create_edition_from_template( ) -> None: # Get the list of available translation files - yaml_files = get_files_from_of_type( - os.sep.join([convert_vars.BASE_PATH, "source"]), "yaml" - ) + yaml_files = get_files_from_of_type(os.sep.join([convert_vars.BASE_PATH, "source"]), "yaml") if not yaml_files: return - mapping: Dict[str, Any] = get_mapping_for_edition( - yaml_files, version, language, edition, template, layout - ) + mapping: Dict[str, Any] = get_mapping_for_edition(yaml_files, version, language, edition, template, layout) if not mapping: logging.warning( @@ -319,9 +305,7 @@ def create_edition_from_template( # return # Get the language data from the correct language file (checks vars.args.language to select the correct file) - language_data: Dict[str, Dict[str, str]] = get_language_data( - yaml_files, language, version, edition - ) + language_data: Dict[str, Dict[str, str]] = get_language_data(yaml_files, language, version, edition) # Transform the language data into the template mapping language_dict: Dict[str, str] = map_language_data_to_template(language_data) @@ -441,9 +425,7 @@ def main() -> None: for language in get_valid_language_choices(): for template in get_valid_templates(): for version in get_valid_version_choices(): - create_edition_from_template( - layout, language, template, version, edition - ) + create_edition_from_template(layout, language, template, version, edition) def parse_arguments(input_args: List[str]) -> argparse.Namespace: @@ -650,9 +632,7 @@ def get_files_from_of_type(path: str, ext: str) -> List[str]: return files -def get_find_replace_list( - meta: Dict[str, str], template: str, layout: str -) -> List[Tuple[str, str]]: +def get_find_replace_list(meta: Dict[str, str], template: str, layout: str) -> List[Tuple[str, str]]: ll: List[Tuple[str, str]] = [ ("_edition", "_" + meta["edition"].lower()), ("_layout", "_" + layout.lower()), @@ -679,15 +659,11 @@ def get_mapping_for_edition( template: str, layout: str, ) -> Dict[str, Any]: - mapping_data: Dict[str, Any] = get_mapping_data_for_edition( - yaml_files, language, version, edition - ) + mapping_data: Dict[str, Any] = get_mapping_data_for_edition(yaml_files, language, version, edition) if not mapping_data: logging.warning("No mapping file found") return {} - if "meta" not in mapping_data or not valid_meta( - mapping_data["meta"], language, edition, version, template, layout - ): + if "meta" not in mapping_data or not valid_meta(mapping_data["meta"], language, edition, version, template, layout): logging.warning("Metadata is missing or invalid in mapping file") return {} try: @@ -723,22 +699,13 @@ def get_mapping_data_for_edition( except yaml.YAMLError as e: logging.info(f"Error loading yaml file: {mappingfile}. Error = {e}") data = {} - if ( - "meta" in data.keys() - and "component" in data["meta"].keys() - and data["meta"]["component"] == "mappings" - ): + if "meta" in data.keys() and "component" in data["meta"].keys() and data["meta"]["component"] == "mappings": logging.debug(" --- found mappings file: " + os.path.split(mappingfile)[1]) else: - logging.debug( - " --- found source file, but it was missing metadata: " - + os.path.split(mappingfile)[1] - ) + logging.debug(" --- found source file, but it was missing metadata: " + os.path.split(mappingfile)[1]) if "meta" in list(data.keys()): meta_keys = data["meta"].keys() - logging.debug( - f" --- data.keys() = {data.keys()}, data[meta].keys() = {meta_keys}" - ) + logging.debug(f" --- data.keys() = {data.keys()}, data[meta].keys() = {meta_keys}") data = {} logging.debug(f" --- Len = {len(data)}.") return data @@ -756,12 +723,8 @@ def build_template_dict(input_data: Dict[str, Any]) -> Dict[str, Any]: text_type = "sentences" logging.debug(f" --- key = {key}.") logging.debug(f" --- suit name = {paragraphs['name']}") - logging.debug( - f" --- suit id = {is_valid_string_argument(paragraphs['id'])}" - ) - full_tag = "${{{}}}".format( - "_".join([is_valid_string_argument(paragraphs["id"]), "suit"]) - ) + logging.debug(f" --- suit id = {is_valid_string_argument(paragraphs['id'])}") + full_tag = "${{{}}}".format("_".join([is_valid_string_argument(paragraphs["id"]), "suit"])) logging.debug(f" --- suit tag = {full_tag}") if data["meta"]["component"] == "cards": data[full_tag] = paragraphs["name"] @@ -834,14 +797,10 @@ def get_language_data( """Get the raw data of the replacement text from correct yaml file""" language_file: str = "" for file in yaml_files: - if is_yaml_file(file) and is_lang_file_for_version( - file, version, language, edition - ): + if is_yaml_file(file) and is_lang_file_for_version(file, version, language, edition): language_file = file if not language_file: - logging.error( - f"Did not find translation for version: {version}, lang: {language}, edition: {edition}" - ) + logging.error(f"Did not find translation for version: {version}, lang: {language}, edition: {edition}") return {} logging.debug(f" --- Loading language file: {language_file}") @@ -892,21 +851,15 @@ def map_language_data_to_template(input_data: Dict[str, Any]) -> Dict[str, str]: try: data = build_template_dict(input_data) except Exception as e: - logging.warning( - f"Could not build valid template mapping. The Yaml file is not valid. Got exception: {e}" - ) + logging.warning(f"Could not build valid template mapping. The Yaml file is not valid. Got exception: {e}") data = input_data if convert_vars.args.debug: debug_txt = " --- Translation data showing First 4 (key: text):\n* " - debug_txt += "\n* ".join( - l1 + ": " + str(data[l1]) for l1 in list(data.keys())[:4] - ) + debug_txt += "\n* ".join(l1 + ": " + str(data[l1]) for l1 in list(data.keys())[:4]) logging.debug(debug_txt) debug_txt = " --- Translation data showing Last 4 (key: text):\n* " - debug_txt += "\n* ".join( - l1 + ": " + str(data[l1]) for l1 in list(data.keys())[-4:] - ) + debug_txt += "\n* ".join(l1 + ": " + str(data[l1]) for l1 in list(data.keys())[-4:]) logging.debug(debug_txt) return data @@ -925,9 +878,7 @@ def get_replacement_mapping_value(k: str, v: str, el_text: str) -> str: return "" -def get_replacement_value_from_dict( - el_text: str, replacement_values: List[Tuple[str, str]] -) -> str: +def get_replacement_value_from_dict(el_text: str, replacement_values: List[Tuple[str, str]]) -> str: # Fast path: if no $ and no OWASP, likely no tags if "$" not in el_text and "OWASP" not in el_text: return el_text @@ -961,9 +912,7 @@ def get_suit_tags_and_key(key: str, edition: str) -> Tuple[List[str], str]: return suit_tags, suit_key -def get_template_for_edition( - layout: str = "guide", template: str = "bridge", edition: str = "webapp" -) -> str: +def get_template_for_edition(layout: str = "guide", template: str = "bridge", edition: str = "webapp") -> str: template_doc: str args_input_file: str = convert_vars.args.inputfile sfile_ext = "idml" @@ -974,38 +923,22 @@ def get_template_for_edition( if os.path.isabs(args_input_file): template_doc = args_input_file elif os.path.isfile(convert_vars.BASE_PATH + os.sep + args_input_file): + template_doc = os.path.normpath(convert_vars.BASE_PATH + os.sep + args_input_file) + elif os.path.isfile(convert_vars.BASE_PATH + os.sep + args_input_file.replace(".." + os.sep, "")): template_doc = os.path.normpath( - convert_vars.BASE_PATH + os.sep + args_input_file - ) - elif os.path.isfile( - convert_vars.BASE_PATH + os.sep + args_input_file.replace(".." + os.sep, "") - ): - template_doc = os.path.normpath( - convert_vars.BASE_PATH - + os.sep - + args_input_file.replace(".." + os.sep, "") + convert_vars.BASE_PATH + os.sep + args_input_file.replace(".." + os.sep, "") ) elif args_input_file.find("..") == -1 and os.path.isfile( convert_vars.BASE_PATH + os.sep + ".." + os.sep + args_input_file ): + template_doc = os.path.normpath(convert_vars.BASE_PATH + os.sep + ".." + os.sep + args_input_file) + elif os.path.isfile(convert_vars.BASE_PATH + os.sep + args_input_file.replace("scripts" + os.sep, "")): template_doc = os.path.normpath( - convert_vars.BASE_PATH + os.sep + ".." + os.sep + args_input_file - ) - elif os.path.isfile( - convert_vars.BASE_PATH - + os.sep - + args_input_file.replace("scripts" + os.sep, "") - ): - template_doc = os.path.normpath( - convert_vars.BASE_PATH - + os.sep - + args_input_file.replace("scripts" + os.sep, "") + convert_vars.BASE_PATH + os.sep + args_input_file.replace("scripts" + os.sep, "") ) else: template_doc = args_input_file - logging.debug( - f" --- Template_doc NOT found. Input File = {args_input_file}" - ) + logging.debug(f" --- Template_doc NOT found. Input File = {args_input_file}") else: # No input file specified - using defaults template_doc = os.path.normpath( @@ -1025,9 +958,7 @@ def get_template_for_edition( logging.debug(f" --- Returning template_doc = {template_doc}") return template_doc else: - logging.error( - f"Source file not found: {template_doc}. Please ensure file exists and try again." - ) + logging.error(f"Source file not found: {template_doc}. Please ensure file exists and try again.") return "None" @@ -1062,10 +993,7 @@ def get_valid_version_choices() -> List[str]: edition: str = convert_vars.args.edition.lower() if convert_vars.args.version.lower() == "all": for version in convert_vars.VERSION_CHOICES: - if ( - version not in ("all", "latest") - and not get_valid_mapping_for_version(version, edition) == "" - ): + if version not in ("all", "latest") and not get_valid_mapping_for_version(version, edition) == "": versions.append(version) elif convert_vars.args.version == "" or convert_vars.args.version == "latest": for version in convert_vars.LATEST_VERSION_CHOICES: @@ -1075,9 +1003,7 @@ def get_valid_version_choices() -> List[str]: versions.append(convert_vars.args.version) if not versions: - logging.debug( - f"No deck with version: {convert_vars.args.version} for edition: {edition} exists" - ) + logging.debug(f"No deck with version: {convert_vars.args.version} for edition: {edition} exists") return versions @@ -1100,10 +1026,7 @@ def get_valid_templates() -> List[str]: def get_valid_edition_choices() -> List[str]: editions = [] - if ( - convert_vars.args.edition.lower() == "all" - or not convert_vars.args.edition.lower() - ): + if convert_vars.args.edition.lower() == "all" or not convert_vars.args.edition.lower(): for edition in convert_vars.EDITION_CHOICES: if edition not in "all": editions.append(edition) @@ -1132,17 +1055,13 @@ def save_docx_file(doc: Any, output_file: str) -> None: doc.save(output_file) -def save_odt_file( - template_doc: str, language_dict: Dict[str, str], output_file: str -) -> None: +def save_odt_file(template_doc: str, language_dict: Dict[str, str], output_file: str) -> None: # Get the output path and temp output path to put the temp xml files output_path = os.path.join(convert_vars.BASE_PATH, "output") temp_output_path = os.path.join(output_path, "temp_odt") # Ensure the output folder and temp output folder exist ensure_folder_exists(temp_output_path) - logging.debug( - " --- temp_folder for extraction of xml files = %s", str(temp_output_path) - ) + logging.debug(" --- temp_folder for extraction of xml files = %s", str(temp_output_path)) # Unzip source xml files and place in temp output folder with zipfile.ZipFile(template_doc) as odt_archive: @@ -1158,9 +1077,7 @@ def save_odt_file( replace_text_in_xml_file(xml_file, replacement_values) # Zip the files as an odt file in output folder - logging.debug( - " --- finished replacing text in xml files. Now zipping into odt file" - ) + logging.debug(" --- finished replacing text in xml files. Now zipping into odt file") zip_dir(temp_output_path, output_file) # If not debugging, delete temp folder and files @@ -1168,17 +1085,13 @@ def save_odt_file( shutil.rmtree(temp_output_path, ignore_errors=True) -def save_idml_file( - template_doc: str, language_dict: Dict[str, str], output_file: str -) -> None: +def save_idml_file(template_doc: str, language_dict: Dict[str, str], output_file: str) -> None: # Get the output path and temp output path to put the temp xml files output_path = convert_vars.BASE_PATH + os.sep + "output" temp_output_path = output_path + os.sep + "temp" # Ensure the output folder and temp output folder exist ensure_folder_exists(temp_output_path) - logging.debug( - " --- temp_folder for extraction of xml files = %s", str(temp_output_path) - ) + logging.debug(" --- temp_folder for extraction of xml files = %s", str(temp_output_path)) # Unzip source xml files and place in temp output folder with zipfile.ZipFile(template_doc) as idml_archive: @@ -1197,9 +1110,7 @@ def save_idml_file( replace_text_in_xml_file(file, replacement_values) # Zip the files as an idml file in output folder - logging.debug( - " --- finished replacing text in xml files. Now zipping into idml file" - ) + logging.debug(" --- finished replacing text in xml files. Now zipping into idml file") zip_dir(temp_output_path, output_file) # If not debugging, delete temp folder and files @@ -1209,13 +1120,9 @@ def save_idml_file( def set_can_convert_to_pdf() -> bool: operating_system: str = sys.platform.lower() - can_convert = ( - operating_system.find("win") != -1 or operating_system.find("darwin") != -1 - ) + can_convert = operating_system.find("win") != -1 or operating_system.find("darwin") != -1 convert_vars.can_convert_to_pdf = can_convert - logging.debug( - f" --- operating system = {operating_system}, can_convert_to_pdf = {convert_vars.can_convert_to_pdf}" - ) + logging.debug(f" --- operating system = {operating_system}, can_convert_to_pdf = {convert_vars.can_convert_to_pdf}") return can_convert @@ -1236,9 +1143,7 @@ def sort_keys_longest_to_shortest( return sorted(new_list, key=lambda s: len(s[0]), reverse=True) -def remove_short_keys( - replacement_dict: Dict[str, str], min_length: int = 8 -) -> Dict[str, str]: +def remove_short_keys(replacement_dict: Dict[str, str], min_length: int = 8) -> Dict[str, str]: data2: Dict[str, str] = {} for key, value in replacement_dict.items(): if len(key) >= min_length: @@ -1250,9 +1155,7 @@ def remove_short_keys( return data2 -def rename_output_file( - file_extension: str, template: str, layout: str, meta: Dict[str, str] -) -> str: +def rename_output_file(file_extension: str, template: str, layout: str, meta: Dict[str, str]) -> str: """Rename output file replacing place-holders from meta dict (edition, component, language, version).""" args_output_file: str = convert_vars.args.outputfile logging.debug(f" --- args_output_file = {args_output_file}") @@ -1261,19 +1164,12 @@ def rename_output_file( if os.path.isabs(args_output_file): output_filename = args_output_file else: - output_filename = str( - Path(convert_vars.BASE_PATH + os.sep + args_output_file) - ) + output_filename = str(Path(convert_vars.BASE_PATH + os.sep + args_output_file)) else: # No output file specified - using default output_filename = str( - Path( - convert_vars.BASE_PATH - + os.sep - + convert_vars.DEFAULT_OUTPUT_FILENAME - + file_extension - ) + Path(convert_vars.BASE_PATH + os.sep + convert_vars.DEFAULT_OUTPUT_FILENAME + file_extension) ) logging.debug(f" --- output_filename before fix extension = {output_filename}") @@ -1333,9 +1229,7 @@ def _find_xml_elements(tree: Any) -> List[ElTree.Element]: return cast(List[ElTree.Element], elements) -def replace_text_in_xml_file( - filename: str, replacement_values: List[Tuple[str, str]] -) -> None: +def replace_text_in_xml_file(filename: str, replacement_values: List[Tuple[str, str]]) -> None: logging.debug(f" --- starting xml_replace for {filename}") try: tree = DefusedElTree.parse(filename) diff --git a/scripts/convert_asvs.py b/scripts/convert_asvs.py index 111479bbc..0867f26e6 100644 --- a/scripts/convert_asvs.py +++ b/scripts/convert_asvs.py @@ -15,16 +15,12 @@ class ConvertVars: - DEFAULT_OUTPUT_PATH = Path( - Path(__file__).parent / "../cornucopia.owasp.org/data/taxonomy/en/ASVS-5.0" - ) + DEFAULT_OUTPUT_PATH = Path(Path(__file__).parent / "../cornucopia.owasp.org/data/taxonomy/en/ASVS-5.0") DEFAULT_INPUT_PATH = ( Path(__file__).parent / "../cornucopia.owasp.org/data/asvs-5.0/en/" "OWASP_Application_Security_Verification_Standard_5.0.0_en.json" ) - DEFAULT_ASVS_TO_CAPEC_INPUT_PATH = ( - Path(__file__).parent / "../source/webapp-asvs-3.0.yaml" - ) + DEFAULT_ASVS_TO_CAPEC_INPUT_PATH = Path(__file__).parent / "../source/webapp-asvs-3.0.yaml" LATEST_CAPEC_VERSION_CHOICES: List[str] = ["3.9"] LATEST_ASVS_VERSION_CHOICES: List[str] = ["5.0"] args: argparse.Namespace @@ -48,21 +44,14 @@ def create_level_summary(level: int, arr: List[dict[str, Any]]) -> None: if link["cat"] != category: category = link["cat"] f.write(f"### {category}\n\n") - shortdesc = ( - link["description"].replace("Verify that", "").strip().capitalize()[0:50] - + " ..." - ) + shortdesc = link["description"].replace("Verify that", "").strip().capitalize()[0:50] + " ..." f.write(f"- [{link['name']}]({link['link']}) *{shortdesc}* \n\n") f.close() def _format_directory_name(ordinal: int, name: str) -> str: """Format ordinal and name into directory-safe string.""" - return ( - str(ordinal).rjust(2, "0") - + "-" - + name.lower().replace(" ", "-").replace(",", "") - ) + return str(ordinal).rjust(2, "0") + "-" + name.lower().replace(" ", "-").replace(",", "") def _get_level_requirement_text(level: int) -> str: @@ -134,10 +123,7 @@ def _write_subitem_content( ) -> None: """Write a single subitem's content to the file.""" file_handle.write("## " + subitem["Shortcode"] + "\n\n") - file_handle.write( - subitem["Description"].encode("ascii", "ignore").decode("utf8", "ignore") - + "\n\n" - ) + file_handle.write(subitem["Description"].encode("ascii", "ignore").decode("utf8", "ignore") + "\n\n") level = int(subitem["L"]) level_text = _get_level_requirement_text(level) @@ -149,9 +135,7 @@ def _write_subitem_content( logging.info("ASVS ID %s has no CAPEC mapping", asvs_id) else: file_handle.write("### Related CAPEC™ Requirements\n\n") - file_handle.write( - f"CAPEC™ ({capec_version}): {create_link_list(asvs_map.get(asvs_id, {}), capec_version)}\n\n" - ) + file_handle.write(f"CAPEC™ ({capec_version}): {create_link_list(asvs_map.get(asvs_id, {}), capec_version)}\n\n") logging.debug("object: %s", subitem) logging.debug("🟪") @@ -180,9 +164,7 @@ def _process_requirement_item( for subitem in item["Items"]: _write_subitem_content(f, subitem, asvs_map, capec_version) - _categorize_by_level( - subitem, requirement_name, item_name, L1, L2, L3, asvs_version - ) + _categorize_by_level(subitem, requirement_name, item_name, L1, L2, L3, asvs_version) _write_disclaimer(f) @@ -199,9 +181,7 @@ def create_asvs_pages( L3: List[dict[str, Any]] = [] for requirement in data["Requirements"]: - requirement_name = _format_directory_name( - requirement["Ordinal"], requirement["Name"] - ) + requirement_name = _format_directory_name(requirement["Ordinal"], requirement["Name"]) logging.debug(requirement_name) os.mkdir(Path(convert_vars.args.output_path, requirement_name)) @@ -222,21 +202,17 @@ def create_asvs_pages( create_level_summary(3, L3) -def has_no_capec_mapping( - asvs_id: str, capec_to_asvs_map: dict[str, dict[str, List[str]]] -) -> bool: - return not capec_to_asvs_map.get(asvs_id) or not capec_to_asvs_map.get( - asvs_id, {"capec_codes": []} - ).get("capec_codes") +def has_no_capec_mapping(asvs_id: str, capec_to_asvs_map: dict[str, dict[str, List[str]]]) -> bool: + return not capec_to_asvs_map.get(asvs_id) or not capec_to_asvs_map.get(asvs_id, {"capec_codes": []}).get( + "capec_codes" + ) def create_link_list(requirements: dict[str, Any], capec_version: str) -> str: link_list = "" asvs_requirements = requirements.get("capec_codes", []) if not asvs_requirements: - logging.debug( - "No CAPEC requirements found in requirements: %s", str(requirements) - ) + logging.debug("No CAPEC requirements found in requirements: %s", str(requirements)) return "" sorted_requirements = sorted(asvs_requirements) for idx, capec_code in enumerate(sorted_requirements): @@ -248,9 +224,7 @@ def create_link_list(requirements: dict[str, Any], capec_version: str) -> str: def parse_arguments(input_args: list[str]) -> argparse.Namespace: - parser = argparse.ArgumentParser( - description="Convert CAPEC™ JSON to Cornucopia format" - ) + parser = argparse.ArgumentParser(description="Convert CAPEC™ JSON to Cornucopia format") parser.add_argument( "-o", "--output-path", @@ -372,9 +346,7 @@ def main() -> None: empty_folder(Path(convert_vars.args.output_path)) create_folder(Path(convert_vars.args.output_path)) data = load_json_file(Path(convert_vars.args.input_path)) - asvs_map: dict[str, dict[str, List[str]]] = load_asvs_to_capec_mapping( - Path(convert_vars.args.asvs_to_capec) - ) + asvs_map: dict[str, dict[str, List[str]]] = load_asvs_to_capec_mapping(Path(convert_vars.args.asvs_to_capec)) create_asvs_pages(data, asvs_map, capec_version, asvs_version) logging.info("ASVS conversion process completed") diff --git a/scripts/convert_capec.py b/scripts/convert_capec.py index 4b7126376..479fa3498 100644 --- a/scripts/convert_capec.py +++ b/scripts/convert_capec.py @@ -15,15 +15,9 @@ class ConvertVars: - DEFAULT_OUTPUT_PATH = ( - Path(__file__).parent / "../cornucopia.owasp.org/data/taxonomy/en/capec-3.9" - ) - DEFAULT_INPUT_PATH = ( - Path(__file__).parent / "../cornucopia.owasp.org/data/capec-3.9/3000.json" - ) - DEFAULT_CAPEC_TO_ASVS_INPUT_PATH = ( - Path(__file__).parent / "../source/webapp-capec-3.0.yaml" - ) + DEFAULT_OUTPUT_PATH = Path(__file__).parent / "../cornucopia.owasp.org/data/taxonomy/en/capec-3.9" + DEFAULT_INPUT_PATH = Path(__file__).parent / "../cornucopia.owasp.org/data/capec-3.9/3000.json" + DEFAULT_CAPEC_TO_ASVS_INPUT_PATH = Path(__file__).parent / "../source/webapp-capec-3.0.yaml" DEFAULT_ASVS_MAPPING_PATH = ( Path(__file__).parent / "../cornucopia.owasp.org/data/asvs-5.0/en/" "OWASP_Application_Security_Verification_Standard_5.0.0_en.json" @@ -51,9 +45,7 @@ def create_capec_pages( f.write("## Description\n\n") f.write(f"{parse_description(i.get('Description', ''))}\n\n") capec_id = int(i["_ID"]) - f.write( - f"Source: [CAPEC™ {capec_id}](https://capec.mitre.org/data/definitions/{capec_id}.html)\n\n" - ) + f.write(f"Source: [CAPEC™ {capec_id}](https://capec.mitre.org/data/definitions/{capec_id}.html)\n\n") if has_no_asvs_mapping(capec_id, capec_to_asvs_map): logging.debug("CAPEC ID %d has no ASVS mapping", capec_id) else: @@ -75,9 +67,7 @@ def create_capec_pages( f.write("## Description\n\n") f.write(f"{parse_description(i.get('Summary', ''))}\n\n") capec_id = int(i["_ID"]) - f.write( - f"Source: [CAPEC™ {capec_id}](https://capec.mitre.org/data/definitions/{capec_id}.html)\n\n" - ) + f.write(f"Source: [CAPEC™ {capec_id}](https://capec.mitre.org/data/definitions/{capec_id}.html)\n\n") if has_no_asvs_mapping(capec_id, capec_to_asvs_map): logging.debug("CAPEC ID %d has no ASVS mapping", capec_id) else: @@ -90,47 +80,31 @@ def create_capec_pages( logging.info("Created %d CAPEC pages", pages) -def has_no_asvs_mapping( - capec_id: int, capec_to_asvs_map: dict[int, dict[str, List[str]]] -) -> bool: - return not capec_to_asvs_map.get(capec_id) or not capec_to_asvs_map.get( - capec_id, {"owasp_asvs": []} - ).get("owasp_asvs") +def has_no_asvs_mapping(capec_id: int, capec_to_asvs_map: dict[int, dict[str, List[str]]]) -> bool: + return not capec_to_asvs_map.get(capec_id) or not capec_to_asvs_map.get(capec_id, {"owasp_asvs": []}).get( + "owasp_asvs" + ) def parse_description(description_field: Any) -> str: """Parse CAPEC description field which can be dict, list, or string.""" if isinstance(description_field, dict): - if ( - "Description" in description_field - and "p" in description_field["Description"] - ): + if "Description" in description_field and "p" in description_field["Description"]: p_content = description_field["Description"]["p"] if isinstance(p_content, dict) and "__text" in p_content: return str(p_content["__text"]) elif isinstance(p_content, list): return " ".join( - [ - ( - str(p["__text"]) - if isinstance(p, dict) and "__text" in p - else str(p) - ) - for p in p_content - ] + [(str(p["__text"]) if isinstance(p, dict) and "__text" in p else str(p)) for p in p_content] ) return str(description_field) -def create_link_list( - requirements: dict[str, Any], asvs_map: dict[str, Any], asvs_version: str -) -> str: +def create_link_list(requirements: dict[str, Any], asvs_map: dict[str, Any], asvs_version: str) -> str: link_list = "" asvs_requirements = requirements.get("owasp_asvs", []) if not asvs_requirements: - logging.debug( - "No ASVS requirements found in requirements: %s", str(requirements) - ) + logging.debug("No ASVS requirements found in requirements: %s", str(requirements)) return "" sorted_requirements = sorted(asvs_requirements) for idx, shortcode in enumerate(sorted_requirements): @@ -143,22 +117,14 @@ def create_link_list( def createlink(data: dict[str, Any], shortcode: str, asvs_version: str) -> str: for i in data["Requirements"]: - name = ( - str(i["Ordinal"]).rjust(2, "0") - + "-" - + i["Name"].lower().replace(" ", "-").replace(",", "") - ) + name = str(i["Ordinal"]).rjust(2, "0") + "-" + i["Name"].lower().replace(" ", "-").replace(",", "") for item in i["Items"]: itemname = ( - str(item["Ordinal"]).rjust(2, "0") - + "-" - + item["Name"].lower().replace(" ", "-").replace(",", "") + str(item["Ordinal"]).rjust(2, "0") + "-" + item["Name"].lower().replace(" ", "-").replace(",", "") ) for subitem in item["Items"]: - if shortcode.removeprefix("V") == subitem["Shortcode"].removeprefix( - "V" - ): + if shortcode.removeprefix("V") == subitem["Shortcode"].removeprefix("V"): return f"[{shortcode}](/taxonomy/asvs-{asvs_version}/{name}/{itemname}#{subitem['Shortcode']})" return shortcode if shortcode else "" @@ -237,9 +203,7 @@ def load_capec_to_asvs_mapping(filepath: Path) -> dict[int, dict[str, List[str]] def parse_arguments(input_args: list[str]) -> argparse.Namespace: - parser = argparse.ArgumentParser( - description="Convert CAPEC JSON to Cornucopia format" - ) + parser = argparse.ArgumentParser(description="Convert CAPEC JSON to Cornucopia format") parser.add_argument( "-o", "--output-path", @@ -316,9 +280,7 @@ def main() -> None: if not asvs_map: logging.error("Failed to load ASVS mapping data") return - capec_to_asvs_map = load_capec_to_asvs_mapping( - Path(convert_vars.args.capec_to_asvs) - ) + capec_to_asvs_map = load_capec_to_asvs_mapping(Path(convert_vars.args.capec_to_asvs)) if not capec_to_asvs_map: logging.error("Failed to load CAPEC to ASVS mapping") return diff --git a/scripts/convert_capec_map_to_asvs_map.py b/scripts/convert_capec_map_to_asvs_map.py index cd02ba658..60279b269 100644 --- a/scripts/convert_capec_map_to_asvs_map.py +++ b/scripts/convert_capec_map_to_asvs_map.py @@ -38,15 +38,11 @@ def extract_asvs_to_capec_mappings(data: dict[str, Any]) -> dict[str, set[str]]: for suit in data["suits"]: _extract_asvs_mapping_from_suit(suit, asvs_to_capec_map) - logging.info( - "Extracted mappings for %d unique ASVS requirements", len(asvs_to_capec_map) - ) + logging.info("Extracted mappings for %d unique ASVS requirements", len(asvs_to_capec_map)) return asvs_to_capec_map -def _extract_asvs_mapping_from_suit( - suit: dict[str, Any], asvs_to_capec_map: dict[str, set[str]] -) -> None: +def _extract_asvs_mapping_from_suit(suit: dict[str, Any], asvs_to_capec_map: dict[str, set[str]]) -> None: """Process a single suit and extract ASVS mappings from its cards.""" if "cards" not in suit: return @@ -55,9 +51,7 @@ def _extract_asvs_mapping_from_suit( _extract_asvs_mapping_from_card(card, asvs_to_capec_map) -def _extract_asvs_mapping_from_card( - card: dict[str, Any], asvs_to_capec_map: dict[str, set[str]] -) -> None: +def _extract_asvs_mapping_from_card(card: dict[str, Any], asvs_to_capec_map: dict[str, set[str]]) -> None: """Process a single card and extract its ASVS mappings.""" if "capec_map" not in card: return @@ -100,9 +94,7 @@ def extract_capec_mappings(data: dict[str, Any]) -> dict[int, set[str]]: return capec_to_asvs_map -def _extract_capec_mapping_from_suit( - suit: dict[str, Any], capec_to_asvs_map: dict[int, set[str]] -) -> None: +def _extract_capec_mapping_from_suit(suit: dict[str, Any], capec_to_asvs_map: dict[int, set[str]]) -> None: """Process a single suit and extract CAPEC mappings from its cards.""" if "cards" not in suit: return @@ -111,9 +103,7 @@ def _extract_capec_mapping_from_suit( _extract_capec_mapping_from_card(card, capec_to_asvs_map) -def _extract_capec_mapping_from_card( - card: dict[str, Any], capec_to_asvs_map: dict[int, set[str]] -) -> None: +def _extract_capec_mapping_from_card(card: dict[str, Any], capec_to_asvs_map: dict[int, set[str]]) -> None: """Process a single card and extract its CAPEC mappings.""" if "capec_map" not in card: return @@ -202,9 +192,7 @@ def save_yaml_file(filepath: Path, data: Dict[str, Any]) -> bool: """Save data as YAML file.""" try: with open(filepath, "w", encoding="utf-8") as f: - yaml.dump( - data, f, default_flow_style=False, sort_keys=False, allow_unicode=True - ) + yaml.dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True) logging.info("Successfully saved YAML file: %s", filepath) return True except Exception as e: @@ -225,9 +213,7 @@ def set_logging() -> None: def parse_arguments(input_args: list[str]) -> argparse.Namespace: """Parse command line arguments.""" - parser = argparse.ArgumentParser( - description="Convert webapp-mappings YAML to CAPEC-to-ASVS mapping format" - ) + parser = argparse.ArgumentParser(description="Convert webapp-mappings YAML to CAPEC-to-ASVS mapping format") parser.add_argument( "-i", "--input-path", @@ -324,22 +310,16 @@ def main() -> None: if convert_vars.args.input_path: input_path = Path(convert_vars.args.input_path).resolve() else: - input_path = directory / ConvertVars.TEMPLATE_FILE_NAME.replace( - "TEMPLATE", "mappings" - ).replace("VERSION", str(convert_vars.args.version)).replace( - "EDITION", str(convert_vars.args.edition) - ) - - capec_output_path = directory / ConvertVars.TEMPLATE_FILE_NAME.replace( - "TEMPLATE", "capec" - ).replace("VERSION", str(convert_vars.args.version)).replace( - "EDITION", str(convert_vars.args.edition) - ) - asvs_output_path = directory / ConvertVars.TEMPLATE_FILE_NAME.replace( - "TEMPLATE", "asvs" - ).replace("VERSION", str(convert_vars.args.version)).replace( - "EDITION", str(convert_vars.args.edition) - ) + input_path = directory / ConvertVars.TEMPLATE_FILE_NAME.replace("TEMPLATE", "mappings").replace( + "VERSION", str(convert_vars.args.version) + ).replace("EDITION", str(convert_vars.args.edition)) + + capec_output_path = directory / ConvertVars.TEMPLATE_FILE_NAME.replace("TEMPLATE", "capec").replace( + "VERSION", str(convert_vars.args.version) + ).replace("EDITION", str(convert_vars.args.edition)) + asvs_output_path = directory / ConvertVars.TEMPLATE_FILE_NAME.replace("TEMPLATE", "asvs").replace( + "VERSION", str(convert_vars.args.version) + ).replace("EDITION", str(convert_vars.args.edition)) logging.info("Input file: %s", input_path) logging.info("Output file: %s", capec_output_path) @@ -379,9 +359,7 @@ def main() -> None: sys.exit(1) logging.info("CAPEC-to-ASVS mapping conversion completed successfully") - logging.info( - "Total CAPEC codes processed: %d", len(output_data) - (1 if meta else 0) - ) + logging.info("Total CAPEC codes processed: %d", len(output_data) - (1 if meta else 0)) asvs_to_capec_map = extract_asvs_to_capec_mappings(data) # Pass enrichment data here diff --git a/tests/scripts/capec_map_enricher_fuzzer.py b/tests/scripts/capec_map_enricher_fuzzer.py index 8a328def8..5bb098324 100644 --- a/tests/scripts/capec_map_enricher_fuzzer.py +++ b/tests/scripts/capec_map_enricher_fuzzer.py @@ -8,9 +8,7 @@ # Add the root directory to sys.path to import scripts sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))) -sys.path.append( - os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "scripts")) -) +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "scripts"))) try: import scripts.capec_map_enricher as enricher @@ -68,10 +66,7 @@ def test_main(data): for _ in range(fdp.ConsumeIntInRange(0, 5)): key = fdp.ConsumeIntInRange(1, 10000) fuzzed_yaml_data[key] = { - "owasp_asvs": [ - fdp.ConsumeUnicodeNoSurrogates(16) - for _ in range(fdp.ConsumeIntInRange(0, 3)) - ] + "owasp_asvs": [fdp.ConsumeUnicodeNoSurrogates(16) for _ in range(fdp.ConsumeIntInRange(0, 3))] } if fdp.ConsumeBool(): fuzzed_yaml_data[key]["extra_field"] = fdp.ConsumeUnicodeNoSurrogates(32) @@ -98,13 +93,9 @@ def test_main(data): try: # Mocking the file I/O to avoid hitting the disk and to feed fuzzed data # We also mock parse_arguments to avoid SystemExit from pathvalidate/argparse early on - with patch.object( - enricher, "load_json_file", return_value=fuzzed_json_data - ), patch.object( + with patch.object(enricher, "load_json_file", return_value=fuzzed_json_data), patch.object( enricher, "load_yaml_file", return_value=fuzzed_yaml_data - ), patch.object( - enricher, "save_yaml_file", return_value=fdp.ConsumeBool() - ), patch.object( + ), patch.object(enricher, "save_yaml_file", return_value=fdp.ConsumeBool()), patch.object( enricher, "parse_arguments", return_value=argparse.Namespace(**argp) ), patch( "sys.argv", ["capec_map_enricher.py"] + args diff --git a/tests/scripts/capec_map_enricher_itest.py b/tests/scripts/capec_map_enricher_itest.py index 0753eb41b..6a8d2d597 100644 --- a/tests/scripts/capec_map_enricher_itest.py +++ b/tests/scripts/capec_map_enricher_itest.py @@ -25,12 +25,8 @@ def setUp(self) -> None: self.base_path = Path(__file__).parent.parent.parent.resolve() # Set up test input file paths - self.test_capec_json = ( - self.base_path / "tests" / "test_files" / "capec-3.9" / "3000.json" - ) - self.test_capec_yaml = ( - self.base_path / "tests" / "test_files" / "source" / "webapp-capec-3.0.yaml" - ) + self.test_capec_json = self.base_path / "tests" / "test_files" / "capec-3.9" / "3000.json" + self.test_capec_yaml = self.base_path / "tests" / "test_files" / "source" / "webapp-capec-3.0.yaml" # Create temporary output directory self.temp_output_dir = tempfile.mkdtemp() @@ -38,13 +34,9 @@ def setUp(self) -> None: # Verify test input files exist if not self.test_capec_json.exists(): - raise FileNotFoundError( - f"Test CAPEC JSON file not found: {self.test_capec_json}" - ) + raise FileNotFoundError(f"Test CAPEC JSON file not found: {self.test_capec_json}") if not self.test_capec_yaml.exists(): - raise FileNotFoundError( - f"Test CAPEC YAML file not found: {self.test_capec_yaml}" - ) + raise FileNotFoundError(f"Test CAPEC YAML file not found: {self.test_capec_yaml}") def tearDown(self) -> None: """Clean up test output directory""" @@ -82,9 +74,7 @@ def test_extract_capec_names_from_test_data(self): # Verify we extracted some names self.assertIsInstance(capec_names, dict) - self.assertGreater( - len(capec_names), 0, "Should extract at least one CAPEC name" - ) + self.assertGreater(len(capec_names), 0, "Should extract at least one CAPEC name") # Verify the structure for capec_id, name in capec_names.items(): @@ -101,9 +91,7 @@ def test_specific_capec_names_in_test_data(self): expected_ids = [1, 5, 560] for capec_id in expected_ids: - self.assertIn( - capec_id, capec_names, f"CAPEC-{capec_id} should be in extracted names" - ) + self.assertIn(capec_id, capec_names, f"CAPEC-{capec_id} should be in extracted names") self.assertIsInstance(capec_names[capec_id], str) self.assertGreater(len(capec_names[capec_id]), 0) @@ -200,20 +188,12 @@ def test_save_and_reload_preserves_data(self): for capec_id in enriched.keys(): self.assertIn(capec_id, reloaded) self.assertEqual(reloaded[capec_id]["name"], enriched[capec_id]["name"]) - self.assertEqual( - reloaded[capec_id]["owasp_asvs"], enriched[capec_id]["owasp_asvs"] - ) + self.assertEqual(reloaded[capec_id]["owasp_asvs"], enriched[capec_id]["owasp_asvs"]) def test_output_to_test_files_directory(self): """Test saving output to the tests/test_files/output directory""" # Set up the expected output path - expected_output_path = ( - self.base_path - / "tests" - / "test_files" - / "output" - / "edition-capec-latest.yaml" - ) + expected_output_path = self.base_path / "tests" / "test_files" / "output" / "edition-capec-latest.yaml" # Create output directory if it doesn't exist expected_output_path.parent.mkdir(parents=True, exist_ok=True) diff --git a/tests/scripts/capec_map_enricher_utest.py b/tests/scripts/capec_map_enricher_utest.py index 10c2acf79..57bb4b420 100644 --- a/tests/scripts/capec_map_enricher_utest.py +++ b/tests/scripts/capec_map_enricher_utest.py @@ -12,9 +12,7 @@ class ConvertVars: OUTPUT_DIR: str = str(Path(__file__).parent.parent.resolve() / "test_files/output") - OUTPUT_FILE: str = str( - Path(__file__).parent.parent.resolve() / OUTPUT_DIR / "enriched_capec.yaml" - ) + OUTPUT_FILE: str = str(Path(__file__).parent.parent.resolve() / OUTPUT_DIR / "enriched_capec.yaml") if "unittest.util" in __import__("sys").modules: @@ -98,11 +96,7 @@ def test_extract_capec_names_missing_attack_pattern(self): def test_extract_capec_names_not_list(self): """Test with Attack_Pattern not being a list""" - data = { - "Attack_Pattern_Catalog": { - "Attack_Patterns": {"Attack_Pattern": "not a list"} - } - } + data = {"Attack_Pattern_Catalog": {"Attack_Patterns": {"Attack_Pattern": "not a list"}}} with self.assertLogs(logging.getLogger(), logging.WARNING) as log: result = enricher.extract_capec_names(data) @@ -222,9 +216,7 @@ def test_load_valid_json(self, mock_file): def test_load_file_not_found(self, mock_file): """Test loading non-existent file""" with self.assertLogs(logging.getLogger(), logging.ERROR) as log: - result = enricher.load_json_file( - Path(ConvertVars.OUTPUT_DIR + "/nonexistent.json") - ) + result = enricher.load_json_file(Path(ConvertVars.OUTPUT_DIR + "/nonexistent.json")) self.assertEqual(result, {}) self.assertIn("File not found", log.output[0]) @@ -233,9 +225,7 @@ def test_load_file_not_found(self, mock_file): def test_load_invalid_json(self, mock_file): """Test loading file with invalid JSON""" with self.assertLogs(logging.getLogger(), logging.ERROR) as log: - result = enricher.load_json_file( - Path(ConvertVars.OUTPUT_DIR + "/invalid.json") - ) + result = enricher.load_json_file(Path(ConvertVars.OUTPUT_DIR + "/invalid.json")) self.assertEqual(result, {}) self.assertIn("Error parsing JSON file", log.output[0]) @@ -266,9 +256,7 @@ def test_load_valid_yaml(self, mock_yaml_load, mock_file): def test_load_yaml_file_not_found(self, mock_file): """Test loading non-existent YAML file""" with self.assertLogs(logging.getLogger(), logging.ERROR) as log: - result = enricher.load_yaml_file( - Path(ConvertVars.OUTPUT_DIR + "/nonexistent.yaml") - ) + result = enricher.load_yaml_file(Path(ConvertVars.OUTPUT_DIR + "/nonexistent.yaml")) self.assertEqual(result, {}) self.assertIn("File not found", log.output[0]) @@ -295,9 +283,7 @@ def test_save_io_error(self, mock_file): data = {1: {"name": "Test"}} with self.assertLogs(logging.getLogger(), logging.ERROR) as log: - result = enricher.save_yaml_file( - Path(ConvertVars.OUTPUT_DIR + "/error.yaml"), data - ) + result = enricher.save_yaml_file(Path(ConvertVars.OUTPUT_DIR + "/error.yaml"), data) self.assertFalse(result) self.assertIn("Error saving YAML file", log.output[0]) diff --git a/tests/scripts/convert_asvs_itest.py b/tests/scripts/convert_asvs_itest.py index 8a0b6ba59..4052d88b6 100644 --- a/tests/scripts/convert_asvs_itest.py +++ b/tests/scripts/convert_asvs_itest.py @@ -33,9 +33,7 @@ def setUp(self) -> None: / "asvs-5.0" / "OWASP_Application_Security_Verification_Standard_5.0.0_en.json" ) - self.test_asvs_to_capec = ( - self.base_path / "tests" / "test_files" / "source" / "webapp-asvs-3.0.yaml" - ) + self.test_asvs_to_capec = self.base_path / "tests" / "test_files" / "source" / "webapp-asvs-3.0.yaml" # Create temporary output directory self.temp_output_dir = tempfile.mkdtemp() @@ -43,13 +41,9 @@ def setUp(self) -> None: # Verify test files exist if not self.test_input_file.exists(): - raise FileNotFoundError( - f"Test input file not found: {self.test_input_file}" - ) + raise FileNotFoundError(f"Test input file not found: {self.test_input_file}") if not self.test_asvs_to_capec.exists(): - raise FileNotFoundError( - f"Test ASVS to CAPEC mapping file not found: {self.test_asvs_to_capec}" - ) + raise FileNotFoundError(f"Test ASVS to CAPEC mapping file not found: {self.test_asvs_to_capec}") def tearDown(self) -> None: """Clean up test output directory""" @@ -64,9 +58,7 @@ def test_load_test_json_file(self): self.assertIsInstance(data, dict) self.assertIn("Requirements", data) self.assertIn("Name", data) - self.assertEqual( - data["Name"], "Application Security Verification Standard Project" - ) + self.assertEqual(data["Name"], "Application Security Verification Standard Project") def test_load_test_yaml_mapping(self): """Test loading the test ASVS to CAPEC YAML mapping file""" @@ -147,16 +139,10 @@ def test_create_asvs_pages_integration(self): # Check that some directories were created output_dirs = [d for d in self.test_output_path.iterdir() if d.is_dir()] - self.assertGreater( - len(output_dirs), 0, "Should have created at least one directory" - ) + self.assertGreater(len(output_dirs), 0, "Should have created at least one directory") # Check for level control directories - level_dirs = [ - d - for d in output_dirs - if d.name.startswith("level-") and d.name.endswith("-controls") - ] + level_dirs = [d for d in output_dirs if d.name.startswith("level-") and d.name.endswith("-controls")] self.assertEqual( len(level_dirs), 3, @@ -193,22 +179,16 @@ def test_create_asvs_pages_with_requirements(self): # Find a requirement directory (e.g., 01-encoding-and-sanitization) requirement_dirs = [ - d - for d in self.test_output_path.iterdir() - if d.is_dir() and not d.name.startswith("level-") + d for d in self.test_output_path.iterdir() if d.is_dir() and not d.name.startswith("level-") ] - self.assertGreater( - len(requirement_dirs), 0, "Should have created requirement directories" - ) + self.assertGreater(len(requirement_dirs), 0, "Should have created requirement directories") # Check first requirement directory first_req_dir = sorted(requirement_dirs)[0] # It should have subdirectories for items item_dirs = [d for d in first_req_dir.iterdir() if d.is_dir()] - self.assertGreater( - len(item_dirs), 0, "Requirement should have item subdirectories" - ) + self.assertGreater(len(item_dirs), 0, "Requirement should have item subdirectories") # Check first item directory for index.md first_item_dir = sorted(item_dirs)[0] @@ -243,9 +223,7 @@ def test_create_asvs_pages_with_capec_links(self): # Look for pages with CAPEC mappings # Check the first requirement requirement_dirs = [ - d - for d in self.test_output_path.iterdir() - if d.is_dir() and not d.name.startswith("level-") + d for d in self.test_output_path.iterdir() if d.is_dir() and not d.name.startswith("level-") ] found_capec_reference = False @@ -269,9 +247,7 @@ def test_create_asvs_pages_with_capec_links(self): def test_main_integration(self): """Test the main function with real test files""" # Create permanent output directory for this test - test_output_dir = ( - self.base_path / "tests" / "test_files" / "output" / "asvs_integration_test" - ) + test_output_dir = self.base_path / "tests" / "test_files" / "output" / "asvs_integration_test" # Clean up if exists if test_output_dir.exists(): @@ -293,9 +269,7 @@ def test_main_integration(self): asvs.convert_vars.args = asvs.parse_arguments(test_args) # Run main workflow (without calling main() to avoid sys.argv issues) - capec_version = asvs.get_valid_capec_version( - asvs.convert_vars.args.capec_version - ) + capec_version = asvs.get_valid_capec_version(asvs.convert_vars.args.capec_version) asvs_version = asvs.get_valid_asvs_version(asvs.convert_vars.args.asvs_version) asvs.set_logging() @@ -303,9 +277,7 @@ def test_main_integration(self): asvs.empty_folder(Path(asvs.convert_vars.args.output_path)) asvs.create_folder(Path(asvs.convert_vars.args.output_path)) data = asvs.load_json_file(Path(asvs.convert_vars.args.input_path)) - asvs_map = asvs.load_asvs_to_capec_mapping( - Path(asvs.convert_vars.args.asvs_to_capec) - ) + asvs_map = asvs.load_asvs_to_capec_mapping(Path(asvs.convert_vars.args.asvs_to_capec)) asvs.create_asvs_pages(data, asvs_map, capec_version, asvs_version) # Verify logging @@ -317,9 +289,7 @@ def test_main_integration(self): # Verify some content was created output_items = list(test_output_dir.iterdir()) - self.assertGreater( - len(output_items), 0, "Should have created output files/directories" - ) + self.assertGreater(len(output_items), 0, "Should have created output files/directories") # Clean up if test_output_dir.exists(): @@ -405,9 +375,7 @@ def test_create_and_empty_folder(self): (sub_folder / "file3.txt").write_text("content3") # Verify content exists - self.assertEqual( - len(list(test_folder.rglob("*"))), 4 - ) # 2 files + 1 folder + 1 file in folder + self.assertEqual(len(list(test_folder.rglob("*"))), 4) # 2 files + 1 folder + 1 file in folder # Empty the folder asvs.empty_folder(test_folder) diff --git a/tests/scripts/convert_asvs_utest.py b/tests/scripts/convert_asvs_utest.py index 6c3ce35f4..170f95430 100644 --- a/tests/scripts/convert_asvs_utest.py +++ b/tests/scripts/convert_asvs_utest.py @@ -50,9 +50,7 @@ def test_create_level_summary_level_1(self, mock_mkdir, mock_file): mock_mkdir.assert_called_once_with(self.mock_output_path / "level-1-controls") # Verify file was opened for writing - mock_file.assert_called_once_with( - self.mock_output_path / "level-1-controls/index.md", "w", encoding="utf-8" - ) + mock_file.assert_called_once_with(self.mock_output_path / "level-1-controls/index.md", "w", encoding="utf-8") # Get the file handle handle = mock_file() @@ -128,9 +126,7 @@ def test_create_link_list_multiple_codes(self): result = asvs.create_link_list(requirements, "3.9") expected = ( - "[120](/taxonomy/capec-3.9/120), " - "[126](/taxonomy/capec-3.9/126), " - "[152](/taxonomy/capec-3.9/152)" + "[120](/taxonomy/capec-3.9/120), " "[126](/taxonomy/capec-3.9/126), " "[152](/taxonomy/capec-3.9/152)" ) self.assertEqual(result, expected) @@ -161,10 +157,7 @@ def test_create_link_list_sorted(self): requirements = {"capec_codes": ["152", "120", "126"]} result = asvs.create_link_list(requirements, "3.9") - expected = ( - "[120](/taxonomy/capec-3.9/120), " - "[126](/taxonomy/capec-3.9/126), [152](/taxonomy/capec-3.9/152)" - ) + expected = "[120](/taxonomy/capec-3.9/120), " "[126](/taxonomy/capec-3.9/126), [152](/taxonomy/capec-3.9/152)" self.assertEqual(result, expected) @@ -177,9 +170,7 @@ def test_parse_arguments_with_defaults(self): self.assertEqual(Path(args.output_path), asvs.ConvertVars.DEFAULT_OUTPUT_PATH) self.assertEqual(Path(args.input_path), asvs.ConvertVars.DEFAULT_INPUT_PATH) - self.assertEqual( - Path(args.asvs_to_capec), asvs.ConvertVars.DEFAULT_ASVS_TO_CAPEC_INPUT_PATH - ) + self.assertEqual(Path(args.asvs_to_capec), asvs.ConvertVars.DEFAULT_ASVS_TO_CAPEC_INPUT_PATH) self.assertEqual(args.capec_version, "3.9") self.assertFalse(args.debug) @@ -480,16 +471,12 @@ def test_create_level_obj(self): "L": "1", } - result = asvs._create_level_obj( - subitem, "01-encoding", "01-architecture", "5.0" - ) + result = asvs._create_level_obj(subitem, "01-encoding", "01-architecture", "5.0") self.assertEqual(result["topic"], "01-encoding") self.assertEqual(result["cat"], "01-architecture") self.assertEqual(result["name"], "V1.1.1") - self.assertEqual( - result["link"], "/taxonomy/asvs-5.0/01-encoding/01-architecture#V1.1.1" - ) + self.assertEqual(result["link"], "/taxonomy/asvs-5.0/01-encoding/01-architecture#V1.1.1") self.assertEqual(result["description"], "Verify that input is decoded") @@ -591,15 +578,11 @@ def test_process_requirement_item(self, mock_mkdir, mock_file): item = { "Ordinal": 1, "Name": "Architecture", - "Items": [ - {"Shortcode": "V1.1.1", "Description": "Test requirement", "L": "1"} - ], + "Items": [{"Shortcode": "V1.1.1", "Description": "Test requirement", "L": "1"}], } L1, L2, L3 = [], [], [] - asvs._process_requirement_item( - item, "01-encoding", {}, "3.9", L1, L2, L3, "5.0" - ) + asvs._process_requirement_item(item, "01-encoding", {}, "3.9", L1, L2, L3, "5.0") # Verify directory was created mock_mkdir.assert_called_once() @@ -698,11 +681,7 @@ def test_create_asvs_pages_with_multiple_levels(self, mock_mkdir, mock_file): mkdir_calls = [str(call_args[0][0]) for call_args in mock_mkdir.call_args_list] # Should include level control directories - level_dirs = [ - call - for call in mkdir_calls - if "level-" in str(call) and "-controls" in str(call) - ] + level_dirs = [call for call in mkdir_calls if "level-" in str(call) and "-controls" in str(call)] self.assertEqual(len(level_dirs), 3) # L1, L2, L3 @patch("builtins.open", new_callable=mock_open) diff --git a/tests/scripts/convert_capec_itest.py b/tests/scripts/convert_capec_itest.py index 43638021d..5063f451a 100644 --- a/tests/scripts/convert_capec_itest.py +++ b/tests/scripts/convert_capec_itest.py @@ -25,9 +25,7 @@ def setUp(self) -> None: self.base_path = Path(__file__).parent.parent.parent.resolve() - self.test_input_file = ( - self.base_path / "tests" / "test_files" / "capec-3.9" / "3000.json" - ) + self.test_input_file = self.base_path / "tests" / "test_files" / "capec-3.9" / "3000.json" self.test_asvs_mapping = ( self.base_path / "tests" @@ -35,25 +33,17 @@ def setUp(self) -> None: / "asvs-5.0" / "OWASP_Application_Security_Verification_Standard_5.0.0_en.json" ) - self.test_capec_to_asvs = ( - self.base_path / "tests" / "test_files" / "source" / "webapp-capec-3.0.yaml" - ) + self.test_capec_to_asvs = self.base_path / "tests" / "test_files" / "source" / "webapp-capec-3.0.yaml" self.temp_output_dir = tempfile.mkdtemp() self.test_output_path = Path(self.temp_output_dir) if not self.test_input_file.exists(): - raise FileNotFoundError( - f"Test input file not found: {self.test_input_file}" - ) + raise FileNotFoundError(f"Test input file not found: {self.test_input_file}") if not self.test_asvs_mapping.exists(): - raise FileNotFoundError( - f"Test ASVS mapping file not found: {self.test_asvs_mapping}" - ) + raise FileNotFoundError(f"Test ASVS mapping file not found: {self.test_asvs_mapping}") if not self.test_capec_to_asvs.exists(): - raise FileNotFoundError( - f"Test CAPEC to ASVS mapping file not found: {self.test_capec_to_asvs}" - ) + raise FileNotFoundError(f"Test CAPEC to ASVS mapping file not found: {self.test_capec_to_asvs}") def tearDown(self) -> None: """Clean up test output directory""" @@ -95,13 +85,9 @@ def test_create_capec_pages_from_test_data(self): capec_to_asvs_map = capec.load_capec_to_asvs_mapping(self.test_capec_to_asvs) capec.create_capec_pages(data, capec_to_asvs_map, asvs_map, "5.0") - attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"][ - "Attack_Pattern" - ] + attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"]["Attack_Pattern"] - self.assertGreater( - len(attack_patterns), 0, "Should have at least one attack pattern" - ) + self.assertGreater(len(attack_patterns), 0, "Should have at least one attack pattern") first_pattern = attack_patterns[0] first_id = str(first_pattern["_ID"]) @@ -110,12 +96,8 @@ def test_create_capec_pages_from_test_data(self): pattern_dir = self.test_output_path / first_id index_file = pattern_dir / "index.md" - self.assertTrue( - pattern_dir.exists(), f"Directory for CAPEC-{first_id} should exist" - ) - self.assertTrue( - index_file.exists(), f"index.md for CAPEC-{first_id} should exist" - ) + self.assertTrue(pattern_dir.exists(), f"Directory for CAPEC-{first_id} should exist") + self.assertTrue(index_file.exists(), f"index.md for CAPEC-{first_id} should exist") content = index_file.read_text(encoding="utf-8") self.assertIn(f"CAPEC™ {first_id}", content) @@ -127,9 +109,7 @@ def test_all_attack_patterns_created(self): """Test that all attack patterns in test data are converted""" data = capec.load_json_file(self.test_input_file) - attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"][ - "Attack_Pattern" - ] + attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"]["Attack_Pattern"] capec.convert_vars.args = argparse.Namespace( output_path=self.test_output_path, @@ -149,9 +129,7 @@ def test_all_attack_patterns_created(self): pattern_id = str(pattern["_ID"]) index_file = self.test_output_path / pattern_id / "index.md" - self.assertTrue( - index_file.exists(), f"index.md should exist for CAPEC-{pattern_id}" - ) + self.assertTrue(index_file.exists(), f"index.md should exist for CAPEC-{pattern_id}") def test_created_file_content_structure(self): """Test that created markdown files have correct structure""" @@ -172,9 +150,7 @@ def test_created_file_content_structure(self): capec_to_asvs_map = capec.load_capec_to_asvs_mapping(self.test_capec_to_asvs) capec.create_capec_pages(data, capec_to_asvs_map, asvs_map, "5.0") - attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"][ - "Attack_Pattern" - ] + attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"]["Attack_Pattern"] pattern = attack_patterns[0] pattern_id = str(pattern["_ID"]) pattern_name = pattern["_Name"] @@ -184,9 +160,7 @@ def test_created_file_content_structure(self): lines = content.split("\n") - self.assertTrue( - lines[0].startswith("# CAPEC™ "), "First line should be title with CAPEC-" - ) + self.assertTrue(lines[0].startswith("# CAPEC™ "), "First line should be title with CAPEC-") self.assertIn(pattern_name, lines[0]) self.assertIn("## Description", content) @@ -265,9 +239,7 @@ def test_full_conversion_workflow(self): with self.assertLogs(logging.getLogger(), logging.INFO): capec.create_capec_pages(data, capec_to_asvs_map, asvs_map, "5.0") - attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"][ - "Attack_Pattern" - ] + attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"]["Attack_Pattern"] for pattern in attack_patterns: pattern_id = str(pattern["_ID"]) @@ -278,9 +250,7 @@ def test_description_parsing_variations(self): """Test that different description formats in test data are parsed correctly""" data = capec.load_json_file(self.test_input_file) - attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"][ - "Attack_Pattern" - ] + attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"]["Attack_Pattern"] for pattern in attack_patterns: description = pattern.get("Description", "") @@ -411,11 +381,7 @@ def test_no_asvs_mapping_output(self): if capec_5_file.exists(): content = capec_5_file.read_text(encoding="utf-8") # Should not have ASVS section if mapping is empty - lines_with_asvs = [ - line - for line in content.split("\n") - if "## Related ASVS Requirements" in line - ] + lines_with_asvs = [line for line in content.split("\n") if "## Related ASVS Requirements" in line] # Either no section, or empty section self.assertTrue(len(lines_with_asvs) == 0 or "ASVS (5.0):" not in content) @@ -426,9 +392,7 @@ class TestConvertCAPECWithOutputDirectory(unittest.TestCase): def setUp(self) -> None: """Set up test environment""" self.base_path = Path(__file__).parent.parent.parent.resolve() - self.test_input_file = ( - self.base_path / "tests" / "test_files" / "capec-3.9" / "3000.json" - ) + self.test_input_file = self.base_path / "tests" / "test_files" / "capec-3.9" / "3000.json" self.test_asvs_mapping = ( self.base_path / "tests" @@ -436,9 +400,7 @@ def setUp(self) -> None: / "asvs-5.0" / "OWASP_Application_Security_Verification_Standard_5.0.0_en.json" ) - self.test_capec_to_asvs = ( - self.base_path / "tests" / "test_files" / "source" / "webapp-capec-3.0.yaml" - ) + self.test_capec_to_asvs = self.base_path / "tests" / "test_files" / "source" / "webapp-capec-3.0.yaml" # Use the actual test output directory (create if doesn't exist) self.test_output_base = self.base_path / "tests" / "test_files" / "output" @@ -475,9 +437,7 @@ def test_conversion_to_test_output_directory(self): capec.create_capec_pages(data, capec_to_asvs_map, asvs_map, "5.0") - attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"][ - "Attack_Pattern" - ] + attack_patterns = data["Attack_Pattern_Catalog"]["Attack_Patterns"]["Attack_Pattern"] first_pattern_id = str(attack_patterns[0]["_ID"]) output_file = self.test_output_path / first_pattern_id / "index.md" diff --git a/tests/scripts/convert_capec_map_to_asvs_map_itest.py b/tests/scripts/convert_capec_map_to_asvs_map_itest.py index b7943d555..704f9015c 100644 --- a/tests/scripts/convert_capec_map_to_asvs_map_itest.py +++ b/tests/scripts/convert_capec_map_to_asvs_map_itest.py @@ -25,26 +25,16 @@ def setUp(self) -> None: self.base_path = Path(__file__).parent.parent.parent.resolve() # Set up test input file path - self.test_input_file = ( - self.base_path - / "tests" - / "test_files" - / "source" - / "webapp-mappings-3.0.yaml" - ) + self.test_input_file = self.base_path / "tests" / "test_files" / "source" / "webapp-mappings-3.0.yaml" # Create temporary output directory self.temp_output_dir = tempfile.mkdtemp() - self.test_capec_output_file = ( - Path(self.temp_output_dir) / "webapp-capec-3.0.yaml" - ) + self.test_capec_output_file = Path(self.temp_output_dir) / "webapp-capec-3.0.yaml" self.test_asvs_output_file = Path(self.temp_output_dir) / "webapp-asvs-3.0.yaml" # Verify test input file exists if not self.test_input_file.exists(): - raise FileNotFoundError( - f"Test input file not found: {self.test_input_file}" - ) + raise FileNotFoundError(f"Test input file not found: {self.test_input_file}") def tearDown(self) -> None: """Clean up test output directory""" @@ -70,18 +60,12 @@ def test_extract_capec_mappings_from_test_data(self): # Verify we extracted some mappings self.assertIsInstance(capec_mapping, dict) - self.assertGreater( - len(capec_mapping), 0, "Should extract at least one CAPEC mapping" - ) + self.assertGreater(len(capec_mapping), 0, "Should extract at least one CAPEC mapping") # Verify the structure of extracted data for capec_code, asvs_set in capec_mapping.items(): - self.assertIsInstance( - capec_code, int, f"CAPEC code {capec_code} should be int" - ) - self.assertIsInstance( - asvs_set, set, f"ASVS set for {capec_code} should be a set" - ) + self.assertIsInstance(capec_code, int, f"CAPEC code {capec_code} should be int") + self.assertIsInstance(asvs_set, set, f"ASVS set for {capec_code} should be a set") def test_extract_asvs_to_capec_mappings_from_test_data(self): """Test extracting ASVS to CAPEC mappings from real test data""" @@ -92,23 +76,15 @@ def test_extract_asvs_to_capec_mappings_from_test_data(self): # Verify we extracted some mappings self.assertIsInstance(asvs_mapping, dict) - self.assertGreater( - len(asvs_mapping), 0, "Should extract at least one ASVS mapping" - ) + self.assertGreater(len(asvs_mapping), 0, "Should extract at least one ASVS mapping") # Verify the structure of extracted data for asvs_req, capec_set in asvs_mapping.items(): - self.assertIsInstance( - asvs_req, str, f"ASVS requirement {asvs_req} should be string" - ) - self.assertIsInstance( - capec_set, set, f"CAPEC set for {asvs_req} should be a set" - ) + self.assertIsInstance(asvs_req, str, f"ASVS requirement {asvs_req} should be string") + self.assertIsInstance(capec_set, set, f"CAPEC set for {asvs_req} should be a set") # Each CAPEC code in the set should be an integer for capec_code in capec_set: - self.assertIsInstance( - capec_code, str, f"CAPEC code {capec_code} should be int" - ) + self.assertIsInstance(capec_code, str, f"CAPEC code {capec_code} should be int") def test_convert_to_output_format_with_test_data(self): """Test converting extracted mappings to output format""" @@ -180,9 +156,7 @@ def test_full_conversion_pipeline(self): self.assertGreater(len(asvs_mapping), 0) # Convert to output format with capec_codes parameter - output_data_asvs = capec_map.convert_to_output_format( - asvs_mapping, parameter="capec_codes" - ) + output_data_asvs = capec_map.convert_to_output_format(asvs_mapping, parameter="capec_codes") # Save ASVS output success = capec_map.save_yaml_file(self.test_asvs_output_file, output_data_asvs) @@ -210,9 +184,7 @@ def test_specific_capec_codes_in_test_data(self): expected_capec_codes = [54, 116, 143, 144] for code in expected_capec_codes: - self.assertIn( - code, capec_mapping, f"CAPEC code {code} should be in mappings" - ) + self.assertIn(code, capec_mapping, f"CAPEC code {code} should be in mappings") def test_specific_asvs_requirements_in_test_data(self): """Test that ASVS requirements from test data are extracted correctly""" @@ -250,12 +222,8 @@ def test_asvs_requirements_merged_correctly(self): def test_output_to_expected_location(self): """Test saving both output files to the expected test location""" # Set up the expected output paths - expected_capec_output_path = ( - self.base_path / "tests" / "test_files" / "output" / "webapp-capec-3.0.yaml" - ) - expected_asvs_output_path = ( - self.base_path / "tests" / "test_files" / "output" / "webapp-asvs-3.0.yaml" - ) + expected_capec_output_path = self.base_path / "tests" / "test_files" / "output" / "webapp-capec-3.0.yaml" + expected_asvs_output_path = self.base_path / "tests" / "test_files" / "output" / "webapp-asvs-3.0.yaml" # Create output directory if it doesn't exist expected_capec_output_path.parent.mkdir(parents=True, exist_ok=True) @@ -279,9 +247,7 @@ def test_output_to_expected_location(self): # Process ASVS mappings asvs_mapping = capec_map.extract_asvs_to_capec_mappings(data) - output_data_asvs = capec_map.convert_to_output_format( - asvs_mapping, parameter="capec_codes" - ) + output_data_asvs = capec_map.convert_to_output_format(asvs_mapping, parameter="capec_codes") # Save ASVS to expected location success = capec_map.save_yaml_file(expected_asvs_output_path, output_data_asvs) @@ -347,9 +313,7 @@ def test_main_function_with_test_files(self): # Extract and convert ASVS mappings asvs_mapping = capec_map.extract_asvs_to_capec_mappings(data) - output_data_asvs = capec_map.convert_to_output_format( - asvs_mapping, parameter="capec_codes" - ) + output_data_asvs = capec_map.convert_to_output_format(asvs_mapping, parameter="capec_codes") # Save ASVS output success = capec_map.save_yaml_file(self.test_asvs_output_file, output_data_asvs) @@ -370,9 +334,7 @@ def test_output_yaml_is_valid(self): # Test ASVS output asvs_mapping = capec_map.extract_asvs_to_capec_mappings(data) - output_data_asvs = capec_map.convert_to_output_format( - asvs_mapping, parameter="capec_codes" - ) + output_data_asvs = capec_map.convert_to_output_format(asvs_mapping, parameter="capec_codes") capec_map.save_yaml_file(self.test_asvs_output_file, output_data_asvs) reloaded_asvs_data = capec_map.load_yaml_file(self.test_asvs_output_file) self.assertEqual(reloaded_asvs_data, output_data_asvs) diff --git a/tests/scripts/convert_capec_map_to_asvs_map_utest.py b/tests/scripts/convert_capec_map_to_asvs_map_utest.py index c9c2b92e6..079b8c3da 100644 --- a/tests/scripts/convert_capec_map_to_asvs_map_utest.py +++ b/tests/scripts/convert_capec_map_to_asvs_map_utest.py @@ -12,9 +12,7 @@ class ConvertVars: OUTPUT_DIR: str = str(Path(__file__).parent.parent.resolve() / "/test_files/output") - OUTPUT_FILE: str = str( - Path(__file__).parent.parent.resolve() / OUTPUT_DIR / "capec_to_asvs_map.yaml" - ) + OUTPUT_FILE: str = str(Path(__file__).parent.parent.resolve() / OUTPUT_DIR / "capec_to_asvs_map.yaml") if "unittest.util" in __import__("sys").modules: @@ -27,19 +25,7 @@ class TestExtractAsvsToCapecMappings(unittest.TestCase): def test_extract_simple_asvs_mapping(self): """Test extracting a simple ASVS to CAPEC mapping""" - data = { - "suits": [ - { - "cards": [ - { - "capec_map": { - 54: {"owasp_asvs": ["4.3.2", "13.2.2", "13.4.1"]} - } - } - ] - } - ] - } + data = {"suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2", "13.2.2", "13.4.1"]}}}]}]} result = capec_map.extract_asvs_to_capec_mappings(data) self.assertIn("4.3.2", result) @@ -112,19 +98,7 @@ class TestExtractCapecMappings(unittest.TestCase): def test_extract_simple_capec_mapping(self): """Test extracting a simple CAPEC mapping""" - data = { - "suits": [ - { - "cards": [ - { - "capec_map": { - 54: {"owasp_asvs": ["4.3.2", "13.2.2", "13.4.1"]} - } - } - ] - } - ] - } + data = {"suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2", "13.2.2", "13.4.1"]}}}]}]} result = capec_map.extract_capec_mappings(data) self.assertIn(54, result) @@ -268,9 +242,7 @@ def test_convert_sorted_keys(self): def test_convert_with_capec_codes_parameter(self): """Test converting ASVS to CAPEC mapping with capec_codes parameter""" asvs_mapping = {"4.3.2": {54, 116}, "13.2.2": {54}} - result = capec_map.convert_to_output_format( - asvs_mapping, parameter="capec_codes" - ) + result = capec_map.convert_to_output_format(asvs_mapping, parameter="capec_codes") self.assertIn("4.3.2", result) self.assertIn("capec_codes", result["4.3.2"]) @@ -296,9 +268,7 @@ def test_load_valid_yaml(self, mock_yaml_load, mock_file): def test_load_file_not_found(self, mock_file): """Test loading non-existent file""" with self.assertLogs(logging.getLogger(), logging.ERROR) as log: - result = capec_map.load_yaml_file( - Path(ConvertVars.OUTPUT_DIR + "nonexistent.yaml") - ) + result = capec_map.load_yaml_file(Path(ConvertVars.OUTPUT_DIR + "nonexistent.yaml")) self.assertEqual(result, {}) self.assertIn("File not found", log.output[0]) @@ -308,9 +278,7 @@ def test_load_file_not_found(self, mock_file): def test_load_yaml_error(self, mock_yaml_load, mock_file): """Test loading file with YAML error""" with self.assertLogs(logging.getLogger(), logging.ERROR) as log: - result = capec_map.load_yaml_file( - Path(ConvertVars.OUTPUT_DIR + "invalid.yaml") - ) + result = capec_map.load_yaml_file(Path(ConvertVars.OUTPUT_DIR + "invalid.yaml")) self.assertEqual(result, {}) self.assertIn("Error loading YAML file", log.output[0]) @@ -345,9 +313,7 @@ def test_save_io_error(self, mock_file): data = {"key": "value"} with self.assertLogs(logging.getLogger(), logging.ERROR) as log: - result = capec_map.save_yaml_file( - Path(ConvertVars.OUTPUT_DIR + "error.yaml"), data - ) + result = capec_map.save_yaml_file(Path(ConvertVars.OUTPUT_DIR + "error.yaml"), data) self.assertFalse(result) self.assertIn("Error saving YAML file", log.output[0]) @@ -477,9 +443,7 @@ class TestMainFunction(unittest.TestCase): @patch("scripts.convert_capec_map_to_asvs_map.load_yaml_file") @patch("scripts.convert_capec_map_to_asvs_map.parse_arguments") @patch("sys.exit") - def test_main_successful_execution( - self, mock_exit, mock_parse_args, mock_load, mock_save - ): + def test_main_successful_execution(self, mock_exit, mock_parse_args, mock_load, mock_save): """Test successful main execution""" # Setup mocks mock_parse_args.return_value = argparse.Namespace( @@ -490,9 +454,7 @@ def test_main_successful_execution( asvs_json=None, debug=False, ) - mock_load.return_value = { - "suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}}}]}] - } + mock_load.return_value = {"suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}}}]}]} mock_save.return_value = True capec_map.main() @@ -540,9 +502,7 @@ def test_main_save_fails(self, mock_exit2, mock_parse_args, mock_load, mock_save asvs_json=None, debug=False, ) - mock_load.return_value = { - "suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}}}]}] - } + mock_load.return_value = {"suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}}}]}]} mock_save.return_value = False with self.assertLogs(logging.getLogger(), logging.ERROR): @@ -559,9 +519,7 @@ def test_main_save_fails(self, mock_exit2, mock_parse_args, mock_load, mock_save @patch("scripts.convert_capec_map_to_asvs_map.load_yaml_file") @patch("scripts.convert_capec_map_to_asvs_map.parse_arguments") @patch("sys.exit") - def test_main_with_asvs_json( - self, mock_exit, mock_parse_args, mock_load_yaml, mock_load_json, mock_save - ): + def test_main_with_asvs_json(self, mock_exit, mock_parse_args, mock_load_yaml, mock_load_json, mock_save): """Test main execution with asvs_json argument""" mock_parse_args.return_value = argparse.Namespace( input_path=Path("input.yaml"), @@ -585,9 +543,7 @@ def test_main_with_asvs_json( ], } mock_load_json.return_value = { - "Requirements": [ - {"Shortcode": "V4.3.2", "Description": "Test description", "L": "L1"} - ] + "Requirements": [{"Shortcode": "V4.3.2", "Description": "Test description", "L": "L1"}] } mock_save.return_value = True @@ -614,9 +570,7 @@ def test_main_with_asvs_json_load_fails( asvs_json="bad_asvs.json", debug=False, ) - mock_load_yaml.return_value = { - "suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}}}]}] - } + mock_load_yaml.return_value = {"suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}}}]}]} mock_load_json.return_value = {} mock_save.return_value = True @@ -640,9 +594,7 @@ def test_main_no_input_path(self, mock_exit, mock_parse_args, mock_load, mock_sa asvs_json=None, debug=False, ) - mock_load.return_value = { - "suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}}}]}] - } + mock_load.return_value = {"suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}}}]}]} mock_save.return_value = True capec_map.main() @@ -655,9 +607,7 @@ def test_main_no_input_path(self, mock_exit, mock_parse_args, mock_load, mock_sa @patch("scripts.convert_capec_map_to_asvs_map.load_yaml_file") @patch("scripts.convert_capec_map_to_asvs_map.parse_arguments") @patch("sys.exit") - def test_main_asvs_save_fails( - self, mock_exit, mock_parse_args, mock_load, mock_save - ): + def test_main_asvs_save_fails(self, mock_exit, mock_parse_args, mock_load, mock_save): """Test main when the second save (ASVS output) fails""" mock_parse_args.return_value = argparse.Namespace( input_path=Path("input.yaml"), @@ -667,9 +617,7 @@ def test_main_asvs_save_fails( asvs_json=None, debug=False, ) - mock_load.return_value = { - "suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}}}]}] - } + mock_load.return_value = {"suits": [{"cards": [{"capec_map": {54: {"owasp_asvs": ["4.3.2"]}}}]}]} # First save succeeds, second save fails mock_save.side_effect = [True, False] @@ -723,11 +671,7 @@ class TestExtractAsvsDetails(unittest.TestCase): def test_extract_simple_requirement(self): """Test extracting a single requirement""" - data = { - "Requirements": [ - {"Shortcode": "V1.1.1", "Description": "Test req", "L": "L1"} - ] - } + data = {"Requirements": [{"Shortcode": "V1.1.1", "Description": "Test req", "L": "L1"}]} result = capec_map.extract_asvs_details(data) self.assertEqual(result, {"1.1.1": {"description": "Test req", "level": "L1"}}) @@ -746,25 +690,13 @@ def test_extract_multiple_requirements(self): def test_extract_nested_structure(self): """Test extracting from nested structure""" - data = { - "chapter": { - "sections": [ - { - "items": [ - {"Shortcode": "V3.3.3", "Description": "Nested", "L": "L3"} - ] - } - ] - } - } + data = {"chapter": {"sections": [{"items": [{"Shortcode": "V3.3.3", "Description": "Nested", "L": "L3"}]}]}} result = capec_map.extract_asvs_details(data) self.assertEqual(result, {"3.3.3": {"description": "Nested", "level": "L3"}}) def test_extract_shortcode_without_v_prefix(self): """Test extracting requirement without V prefix""" - data = { - "Requirements": [{"Shortcode": "4.4.4", "Description": "No V", "L": "L1"}] - } + data = {"Requirements": [{"Shortcode": "4.4.4", "Description": "No V", "L": "L1"}]} result = capec_map.extract_asvs_details(data) self.assertEqual(result, {"4.4.4": {"description": "No V", "level": "L1"}}) @@ -800,9 +732,7 @@ def test_convert_with_enrichment_data(self): """Test converting with enrichment data""" capec_map_data = {"1.1.1": {"4.3.2"}} enrichment = {"1.1.1": {"description": "Test desc", "level": "L1"}} - result = capec_map.convert_to_output_format( - capec_map_data, parameter="capec_codes", enrichment_data=enrichment - ) + result = capec_map.convert_to_output_format(capec_map_data, parameter="capec_codes", enrichment_data=enrichment) self.assertIn("1.1.1", result) self.assertEqual(result["1.1.1"]["description"], "Test desc") self.assertEqual(result["1.1.1"]["level"], "L1") @@ -812,9 +742,7 @@ def test_convert_with_partial_enrichment(self): """Test converting when enrichment data is missing for some keys""" capec_map_data = {"1.1.1": {"4.3.2"}, "2.2.2": {"5.5.5"}} enrichment = {"1.1.1": {"description": "Only first", "level": "L1"}} - result = capec_map.convert_to_output_format( - capec_map_data, parameter="capec_codes", enrichment_data=enrichment - ) + result = capec_map.convert_to_output_format(capec_map_data, parameter="capec_codes", enrichment_data=enrichment) self.assertIn("description", result["1.1.1"]) self.assertNotIn("description", result["2.2.2"]) diff --git a/tests/scripts/convert_capec_utest.py b/tests/scripts/convert_capec_utest.py index 63824cf42..e6f9bddae 100644 --- a/tests/scripts/convert_capec_utest.py +++ b/tests/scripts/convert_capec_utest.py @@ -76,9 +76,7 @@ def test_validate_json_data_valid(self): """Test validation with valid data structure""" data = { "Attack_Pattern_Catalog": { - "Attack_Patterns": { - "Attack_Pattern": [{"_ID": "1", "_Name": "Test Pattern"}] - }, + "Attack_Patterns": {"Attack_Pattern": [{"_ID": "1", "_Name": "Test Pattern"}]}, "Categories": { "Category": [ { @@ -154,11 +152,7 @@ def test_validate_json_data_missing_attack_pattern(self): def test_validate_json_data_attack_pattern_not_list(self): """Test validation when Attack_Pattern is not a list""" - data = { - "Attack_Pattern_Catalog": { - "Attack_Patterns": {"Attack_Pattern": "not a list"} - } - } + data = {"Attack_Pattern_Catalog": {"Attack_Patterns": {"Attack_Pattern": "not a list"}}} with self.assertLogs(logging.getLogger(), logging.ERROR) as log: result = capec.validate_json_data(data) self.assertFalse(result) @@ -191,9 +185,7 @@ def test_load_json_file_invalid_json(self): mock_file = mock_open(read_data="invalid json content {") with patch("builtins.open", mock_file): - with patch( - "json.load", side_effect=json.JSONDecodeError("Invalid", "doc", 0) - ): + with patch("json.load", side_effect=json.JSONDecodeError("Invalid", "doc", 0)): with self.assertLogs(logging.getLogger(), logging.ERROR) as log: result = capec.load_json_file(Path("/test/file.json")) @@ -372,9 +364,7 @@ def test_create_capec_pages_single_pattern(self, mock_create_folder, mock_file): with patch.object(Path, "parent") as mock_parent: mock_parent.resolve.return_value = Path("/mock/directory") - capec.create_capec_pages( - test_data, test_capec_to_asvs_map, test_asvs_map, "5.0" - ) + capec.create_capec_pages(test_data, test_capec_to_asvs_map, test_asvs_map, "5.0") # Verify create_folder was called mock_create_folder.assert_called() @@ -450,9 +440,7 @@ def test_create_capec_pages_multiple_patterns(self, mock_create_folder, mock_fil with patch.object(Path, "parent") as mock_parent: mock_parent.resolve.return_value = Path("/mock/directory") - capec.create_capec_pages( - test_data, test_capec_to_asvs_map, test_asvs_map, "5.0" - ) + capec.create_capec_pages(test_data, test_capec_to_asvs_map, test_asvs_map, "5.0") # Verify create_folder was called twice (once for each pattern) self.assertEqual(mock_create_folder.call_count, 3) @@ -462,9 +450,7 @@ def test_create_capec_pages_multiple_patterns(self, mock_create_folder, mock_fil @patch("builtins.open", new_callable=mock_open) @patch("scripts.convert_capec.create_folder") - def test_create_capec_pages_complex_description( - self, mock_create_folder, mock_file - ): + def test_create_capec_pages_complex_description(self, mock_create_folder, mock_file): """Test creating CAPEC pages with complex description format""" # Setup test_output_path = Path("/test/output") @@ -479,11 +465,7 @@ def test_create_capec_pages_complex_description( { "_ID": "456", "_Name": "Complex Pattern", - "Description": { - "Description": { - "p": {"__text": "Complex description text"} - } - }, + "Description": {"Description": {"p": {"__text": "Complex description text"}}}, } ] }, @@ -516,9 +498,7 @@ def test_create_capec_pages_complex_description( with patch.object(Path, "parent") as mock_parent: mock_parent.resolve.return_value = Path("/mock/directory") - capec.create_capec_pages( - test_data, test_capec_to_asvs_map, test_asvs_map, "5.0" - ) + capec.create_capec_pages(test_data, test_capec_to_asvs_map, test_asvs_map, "5.0") # Get the written content handle = mock_file() @@ -682,9 +662,7 @@ def test_createlink_found(self): { "Ordinal": 1, "Name": "General Authorization Design", - "Items": [ - {"Shortcode": "V8.1.1", "Text": "Test requirement"} - ], + "Items": [{"Shortcode": "V8.1.1", "Text": "Test requirement"}], } ], } @@ -867,9 +845,7 @@ def test_create_capec_pages_with_asvs_mapping(self, mock_create_folder, mock_fil with patch.object(Path, "parent") as mock_parent: mock_parent.resolve.return_value = Path("/mock/directory") - capec.create_capec_pages( - test_data, test_capec_to_asvs_map, test_asvs_map, "5.0" - ) + capec.create_capec_pages(test_data, test_capec_to_asvs_map, test_asvs_map, "5.0") handle = mock_file() written_content = "".join(call.args[0] for call in handle.write.call_args_list) diff --git a/tests/scripts/convert_fuzzer.py b/tests/scripts/convert_fuzzer.py index a4b58d1c5..36131486c 100644 --- a/tests/scripts/convert_fuzzer.py +++ b/tests/scripts/convert_fuzzer.py @@ -64,9 +64,7 @@ def test_main(data): try: with patch.object(argparse, "ArgumentParser") as mock_parser: - mock_parser.return_value.parse_args.return_value = argparse.Namespace( - **argp - ) + mock_parser.return_value.parse_args.return_value = argparse.Namespace(**argp) with test.assertLogs(logging.getLogger(), logging.DEBUG) as l6: with patch("sys.argv", args): c.main() @@ -97,9 +95,7 @@ def test_main(data): "outputfile": "", } - mock_parser.return_value.parse_args.return_value = argparse.Namespace( - **argp - ) + mock_parser.return_value.parse_args.return_value = argparse.Namespace(**argp) with test.assertLogs(logging.getLogger(), logging.DEBUG) as l6: with patch("sys.argv", args): c.main() @@ -130,9 +126,7 @@ def test_main(data): "outputfile": "", } - mock_parser.return_value.parse_args.return_value = argparse.Namespace( - **argp - ) + mock_parser.return_value.parse_args.return_value = argparse.Namespace(**argp) with test.assertLogs(logging.getLogger(), logging.DEBUG) as l6: with patch("sys.argv", args): c.main() @@ -163,9 +157,7 @@ def test_main(data): "outputfile": "", } - mock_parser.return_value.parse_args.return_value = argparse.Namespace( - **argp - ) + mock_parser.return_value.parse_args.return_value = argparse.Namespace(**argp) with test.assertLogs(logging.getLogger(), logging.DEBUG) as l6: with patch("sys.argv", args): c.main() @@ -196,9 +188,7 @@ def test_main(data): "outputfile": "", } - mock_parser.return_value.parse_args.return_value = argparse.Namespace( - **argp - ) + mock_parser.return_value.parse_args.return_value = argparse.Namespace(**argp) with test.assertLogs(logging.getLogger(), logging.DEBUG) as l6: with patch("sys.argv", args): c.main() @@ -229,9 +219,7 @@ def test_main(data): "outputfile": "", } - mock_parser.return_value.parse_args.return_value = argparse.Namespace( - **argp - ) + mock_parser.return_value.parse_args.return_value = argparse.Namespace(**argp) with test.assertLogs(logging.getLogger(), logging.DEBUG) as l6: with patch("sys.argv", args): c.main() @@ -264,9 +252,7 @@ def test_main(data): "outputfile": outputfile, } - mock_parser.return_value.parse_args.return_value = argparse.Namespace( - **argp - ) + mock_parser.return_value.parse_args.return_value = argparse.Namespace(**argp) with test.assertLogs(logging.getLogger(), logging.DEBUG) as l6: with patch("sys.argv", args): c.main() @@ -297,9 +283,7 @@ def test_main(data): "outputfile": "", } - mock_parser.return_value.parse_args.return_value = argparse.Namespace( - **argp - ) + mock_parser.return_value.parse_args.return_value = argparse.Namespace(**argp) with test.assertLogs(logging.getLogger(), logging.DEBUG) as l6: with patch("sys.argv", args): c.main() diff --git a/tests/scripts/convert_itest.py b/tests/scripts/convert_itest.py index aa5b4e1ec..c8a9f78ce 100644 --- a/tests/scripts/convert_itest.py +++ b/tests/scripts/convert_itest.py @@ -71,9 +71,7 @@ def tearDown(self) -> None: def test_main(self): with patch.object(argparse, "ArgumentParser") as mock_parser: - mock_parser.return_value.parse_args.return_value = argparse.Namespace( - **self.argp - ) + mock_parser.return_value.parse_args.return_value = argparse.Namespace(**self.argp) with self.assertLogs(logging.getLogger(), logging.INFO) as l6: with patch("sys.argv", self.args): c.main() diff --git a/tests/scripts/convert_utest.py b/tests/scripts/convert_utest.py index abf03996a..3fc4966dc 100644 --- a/tests/scripts/convert_utest.py +++ b/tests/scripts/convert_utest.py @@ -17,9 +17,7 @@ import scripts.convert as c c.convert_vars = c.ConvertVars() -c.convert_vars.BASE_PATH = os.path.split(os.path.dirname(os.path.realpath(c.__file__)))[ - 0 -] +c.convert_vars.BASE_PATH = os.path.split(os.path.dirname(os.path.realpath(c.__file__)))[0] if "unittest.util" in __import__("sys").modules: @@ -66,11 +64,7 @@ def test_is_valid_string_argument(self) -> None: ) ) # Arabic - self.assertTrue( - c.is_valid_string_argument( - "ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوي ًٌٍَُِّْٰﷲﷴﷺﷻ ٠١٢٣٤٥٦٧٨٩ " - ) - ) + self.assertTrue(c.is_valid_string_argument("ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوي ًٌٍَُِّْٰﷲﷴﷺﷻ ٠١٢٣٤٥٦٧٨٩ ")) # European self.assertTrue( c.is_valid_string_argument( @@ -224,10 +218,7 @@ def test_get_valid_language_choices_all(self) -> None: class TestSetCanConvertToPdf(unittest.TestCase): def test_set_can_convert_to_pdf(self) -> None: - want_can_convert_in = ( - sys.platform.lower().find("win") != -1 - or sys.platform.lower().find("darwin") != -1 - ) + want_can_convert_in = sys.platform.lower().find("win") != -1 or sys.platform.lower().find("darwin") != -1 c.set_can_convert_to_pdf() got_can_convert = c.convert_vars.can_convert_to_pdf @@ -400,9 +391,7 @@ def test_get_template_for_edition_relative_path_root(self) -> None: def test_get_template_for_edition_file_not_exist(self) -> None: template_docx_filename = os.path.normpath( - os.path.join( - "resources", "templates", "owasp_cornucopia_template_template.docx" - ) + os.path.join("resources", "templates", "owasp_cornucopia_template_template.docx") ) c.convert_vars.args.inputfile = template_docx_filename layout = "guide" @@ -410,8 +399,7 @@ def test_get_template_for_edition_file_not_exist(self) -> None: edition = "webapp" want_template_doc = "None" want_error_log_messages = [ - f"ERROR:root:Source file not found: {template_docx_filename}. " - "Please ensure file exists and try again." + f"ERROR:root:Source file not found: {template_docx_filename}. " "Please ensure file exists and try again." ] with self.assertLogs(logging.getLogger(), logging.ERROR) as ll: @@ -422,9 +410,7 @@ def test_get_template_for_edition_file_not_exist(self) -> None: class TestRenameOutputFile(unittest.TestCase): def setUp(self) -> None: - c.convert_vars.args = argparse.Namespace( - outputfile=c.convert_vars.DEFAULT_OUTPUT_FILENAME - ) + c.convert_vars.args = argparse.Namespace(outputfile=c.convert_vars.DEFAULT_OUTPUT_FILENAME) self.input_meta_data = { "edition": "webapp", "component": "cards", @@ -436,35 +422,23 @@ def tearDown(self) -> None: c.convert_vars.args.outputfile = "" def test_rename_output_file_short(self) -> None: - c.convert_vars.args.outputfile = os.path.join( - "output", "cornucopia_edition_ver_layout_lang.docx" - ) + c.convert_vars.args.outputfile = os.path.join("output", "cornucopia_edition_ver_layout_lang.docx") file_extension = ".docx" template = "bridge" layout = "guide" - want_filename = os.path.join( - c.convert_vars.BASE_PATH, "output", "cornucopia_webapp_3.0_guide_en.docx" - ) + want_filename = os.path.join(c.convert_vars.BASE_PATH, "output", "cornucopia_webapp_3.0_guide_en.docx") - got_filename = c.rename_output_file( - file_extension, template, layout, self.input_meta_data - ) + got_filename = c.rename_output_file(file_extension, template, layout, self.input_meta_data) self.assertEqual(want_filename, got_filename) def test_rename_output_file_no_extension(self) -> None: - c.convert_vars.args.outputfile = ( - "output" + os.sep + "cornucopia_edition_ver_layout_lang" - ) + c.convert_vars.args.outputfile = "output" + os.sep + "cornucopia_edition_ver_layout_lang" file_extension = ".idml" template = "bridge" layout = "guide" - want_filename = os.path.join( - c.convert_vars.BASE_PATH, "output", "cornucopia_webapp_3.0_guide_en.idml" - ) + want_filename = os.path.join(c.convert_vars.BASE_PATH, "output", "cornucopia_webapp_3.0_guide_en.idml") - got_filename = c.rename_output_file( - file_extension, template, layout, self.input_meta_data - ) + got_filename = c.rename_output_file(file_extension, template, layout, self.input_meta_data) self.assertEqual(want_filename, got_filename) def test_rename_output_file_using_defaults(self) -> None: @@ -478,9 +452,7 @@ def test_rename_output_file_using_defaults(self) -> None: "owasp_cornucopia_webapp_3.0_guide_bridge_en.docx", ) - got_filename = c.rename_output_file( - file_extension, template, layout, self.input_meta_data - ) + got_filename = c.rename_output_file(file_extension, template, layout, self.input_meta_data) self.assertEqual(want_filename, got_filename) def test_rename_output_file_blank(self) -> None: @@ -494,9 +466,7 @@ def test_rename_output_file_blank(self) -> None: "owasp_cornucopia_webapp_3.0_guide_bridge_en.docx", ) - got_filename = c.rename_output_file( - file_extension, template, layout, self.input_meta_data - ) + got_filename = c.rename_output_file(file_extension, template, layout, self.input_meta_data) self.assertEqual(want_filename, got_filename) def test_rename_output_file_template(self) -> None: @@ -510,9 +480,7 @@ def test_rename_output_file_template(self) -> None: "owasp_cornucopia_webapp_3.0_guide_bridge_en.docx", ) - got_filename = c.rename_output_file( - file_extension, template, layout, self.input_meta_data - ) + got_filename = c.rename_output_file(file_extension, template, layout, self.input_meta_data) self.assertEqual(want_filename, got_filename) @@ -558,9 +526,7 @@ def test_valid_meta(self) -> None: "-> languages section in the mappings file" ) with self.assertLogs(logging.getLogger(), logging.WARNING) as ll: - valid: bool = c.valid_meta( - self.meta, "fr", "webapp", "3.0", "bridge", "cards" - ) + valid: bool = c.valid_meta(self.meta, "fr", "webapp", "3.0", "bridge", "cards") self.assertFalse(valid) self.assertIn(want_logging_error_message, " ".join(ll.output)) @@ -611,9 +577,7 @@ def test_get_meta_data_failure(self) -> None: input_data = self.test_data.copy() del input_data["meta"] want_data: Dict[str, str] = {} - want_logging_error_message = [ - "ERROR:root:Could not find meta tag in the language data." - ] + want_logging_error_message = ["ERROR:root:Could not find meta tag in the language data."] with self.assertLogs(logging.getLogger(), logging.ERROR) as ll: got_data = c.get_meta_data(input_data) @@ -623,9 +587,7 @@ def test_get_meta_data_failure(self) -> None: class TestGetLanguageData(unittest.TestCase): def setUp(self) -> None: - test_source_yaml = os.path.join( - c.convert_vars.BASE_PATH, "tests", "test_files", "source", "*.yaml" - ) + test_source_yaml = os.path.join(c.convert_vars.BASE_PATH, "tests", "test_files", "source", "*.yaml") self.input_yaml_files = glob.glob(test_source_yaml) self.input_language = "en" self.test_data: Dict[str, Any] = { @@ -696,19 +658,13 @@ def test_get_language_data_translation_en_first_suit_first_card(self) -> None: want_first_suit_first_card_keys = self.test_data["suits"][0]["cards"][0].keys() want_first_suit_first_card_value = self.test_data["suits"][0]["cards"][0]["id"] - got_suits = c.get_language_data(self.input_yaml_files, self.input_language)[ - "suits" - ] + got_suits = c.get_language_data(self.input_yaml_files, self.input_language)["suits"] got_first_suit_keys = got_suits[0].keys() self.assertEqual(want_first_suit_keys, got_first_suit_keys) got_first_suit_first_card_keys = got_suits[0]["cards"][0].keys() - self.assertEqual( - want_first_suit_first_card_keys, got_first_suit_first_card_keys - ) + self.assertEqual(want_first_suit_first_card_keys, got_first_suit_first_card_keys) got_first_suit_first_card_value = got_suits[0]["cards"][0]["id"] - self.assertEqual( - want_first_suit_first_card_value, got_first_suit_first_card_value - ) + self.assertEqual(want_first_suit_first_card_value, got_first_suit_first_card_value) def test_get_language_data_translation_es_first_suit_first_card(self) -> None: want_first_suit_keys = self.test_data["suits"][0].keys() @@ -719,13 +675,9 @@ def test_get_language_data_translation_es_first_suit_first_card(self) -> None: got_first_suit_keys = got_suits[0].keys() self.assertEqual(want_first_suit_keys, got_first_suit_keys) got_first_suit_first_card_keys = got_suits[0]["cards"][0].keys() - self.assertEqual( - want_first_suit_first_card_keys, got_first_suit_first_card_keys - ) + self.assertEqual(want_first_suit_first_card_keys, got_first_suit_first_card_keys) got_first_suit_first_card_value = got_suits[0]["cards"][0]["id"] - self.assertEqual( - want_first_suit_first_card_value, got_first_suit_first_card_value - ) + self.assertEqual(want_first_suit_first_card_value, got_first_suit_first_card_value) def test_get_mapping_data_for_edition(self) -> None: want_meta = { @@ -737,9 +689,7 @@ def test_get_mapping_data_for_edition(self) -> None: "layouts": ["cards", "leaflet", "guide"], "templates": ["bridge_qr", "bridge", "tarot", "tarot_qr"], } - got_data = c.get_mapping_data_for_edition( - self.input_yaml_files, self.input_language, "3.0", "webapp" - ) + got_data = c.get_mapping_data_for_edition(self.input_yaml_files, self.input_language, "3.0", "webapp") self.assertEqual(want_meta, got_data["meta"]) def test_get_mapping_data_for_edition_first_suit_first_card(self) -> None: @@ -942,9 +892,7 @@ def test_get_mapping_data_for_edition_first_suit_first_card(self) -> None: }, } - got_suits = c.get_mapping_data_for_edition( - self.input_yaml_files, self.input_language - )["suits"] + got_suits = c.get_mapping_data_for_edition(self.input_yaml_files, self.input_language)["suits"] got_first_suit_keys = got_suits[0].keys() self.assertEqual(want_first_suit_keys, got_first_suit_keys) got_first_suit_first_card = got_suits[0]["cards"][0] @@ -953,9 +901,7 @@ def test_get_mapping_data_for_edition_first_suit_first_card(self) -> None: class TestGetLanguageDataFor1dot30(unittest.TestCase): def setUp(self) -> None: - test_source_yaml = os.path.join( - c.convert_vars.BASE_PATH, "tests", "test_files", "source", "*.yaml" - ) + test_source_yaml = os.path.join(c.convert_vars.BASE_PATH, "tests", "test_files", "source", "*.yaml") self.input_yaml_files = glob.glob(test_source_yaml) self.input_language = "en" self.input_version = "3.0" @@ -1013,9 +959,7 @@ def test_get_language_data_translation_meta(self) -> None: "version": "3.0", } - got_data = c.get_language_data( - self.input_yaml_files, self.input_language, self.input_version - ) + got_data = c.get_language_data(self.input_yaml_files, self.input_language, self.input_version) self.assertEqual(want_meta, got_data["meta"]) def test_get_language_data_translation_en_first_suit_first_card(self) -> None: @@ -1023,19 +967,13 @@ def test_get_language_data_translation_en_first_suit_first_card(self) -> None: want_first_suit_first_card_keys = self.test_data["suits"][0]["cards"][0].keys() want_first_suit_first_card_value = self.test_data["suits"][0]["cards"][0]["id"] - got_suits = c.get_language_data( - self.input_yaml_files, self.input_language, self.input_version - )["suits"] + got_suits = c.get_language_data(self.input_yaml_files, self.input_language, self.input_version)["suits"] got_first_suit_keys = got_suits[0].keys() self.assertEqual(want_first_suit_keys, got_first_suit_keys) got_first_suit_first_card_keys = got_suits[0]["cards"][0].keys() - self.assertEqual( - want_first_suit_first_card_keys, got_first_suit_first_card_keys - ) + self.assertEqual(want_first_suit_first_card_keys, got_first_suit_first_card_keys) got_first_suit_first_card_value = got_suits[0]["cards"][0]["id"] - self.assertEqual( - want_first_suit_first_card_value, got_first_suit_first_card_value - ) + self.assertEqual(want_first_suit_first_card_value, got_first_suit_first_card_value) def test_get_language_data_translation_es_first_suit_first_card(self) -> None: input_language = "es" @@ -1043,19 +981,13 @@ def test_get_language_data_translation_es_first_suit_first_card(self) -> None: want_first_suit_first_card_keys = self.test_data["suits"][0]["cards"][0].keys() want_first_suit_first_card_value = self.test_data["suits"][0]["cards"][0]["id"] - got_suits = c.get_language_data( - self.input_yaml_files, input_language, self.input_version - )["suits"] + got_suits = c.get_language_data(self.input_yaml_files, input_language, self.input_version)["suits"] got_first_suit_keys = got_suits[0].keys() self.assertEqual(want_first_suit_keys, got_first_suit_keys) got_first_suit_first_card_keys = got_suits[0]["cards"][0].keys() - self.assertEqual( - want_first_suit_first_card_keys, got_first_suit_first_card_keys - ) + self.assertEqual(want_first_suit_first_card_keys, got_first_suit_first_card_keys) got_first_suit_first_card_value = got_suits[0]["cards"][0]["id"] - self.assertEqual( - want_first_suit_first_card_value, got_first_suit_first_card_value - ) + self.assertEqual(want_first_suit_first_card_value, got_first_suit_first_card_value) def test_get_mapping_data_for_edition_asvs4(self) -> None: want_meta = { @@ -1067,9 +999,7 @@ def test_get_mapping_data_for_edition_asvs4(self) -> None: "templates": ["bridge_qr", "bridge", "tarot", "tarot_qr"], "languages": ["en", "es"], } - got_data = c.get_mapping_data_for_edition( - self.input_yaml_files, self.input_language, self.input_version - ) + got_data = c.get_mapping_data_for_edition(self.input_yaml_files, self.input_language, self.input_version) self.assertEqual(want_meta, got_data["meta"]) def test_get_mapping_data_for_edition_first_suit_first_card_asvs4(self) -> None: @@ -1272,9 +1202,9 @@ def test_get_mapping_data_for_edition_first_suit_first_card_asvs4(self) -> None: }, } - got_suits = c.get_mapping_data_for_edition( - self.input_yaml_files, self.input_language, self.input_version - )["suits"] + got_suits = c.get_mapping_data_for_edition(self.input_yaml_files, self.input_language, self.input_version)[ + "suits" + ] got_first_suit_keys = got_suits[0].keys() self.assertEqual(want_first_suit_keys, got_first_suit_keys) got_first_suit_first_card = got_suits[0]["cards"][0] @@ -1390,13 +1320,9 @@ def test_get_files_from_of_type_source_yaml_files(self) -> None: def test_get_files_from_of_type_source_docx_files(self) -> None: c.convert_vars.args = argparse.Namespace(debug=False) - path = os.path.join( - c.convert_vars.BASE_PATH, "tests", "test_files", "resources", "templates" - ) + path = os.path.join(c.convert_vars.BASE_PATH, "tests", "test_files", "resources", "templates") ext = "docx" - want_files = [ - path + os.sep + "owasp_cornucopia_webapp_ver_guide_bridge_lang.docx" - ] + want_files = [path + os.sep + "owasp_cornucopia_webapp_ver_guide_bridge_lang.docx"] got_files = c.get_files_from_of_type(path, ext) self.assertEqual(len(want_files), len(got_files)) @@ -1409,8 +1335,7 @@ def test_get_files_from_of_type_source_empty_list(self) -> None: ext = "ext" want_files: typing.List[str] = [] want_logging_error_message = [ - "ERROR:root:No language files found in folder: " - + str(os.path.join(c.convert_vars.BASE_PATH, "source")) + "ERROR:root:No language files found in folder: " + str(os.path.join(c.convert_vars.BASE_PATH, "source")) ] with self.assertLogs(logging.getLogger(), logging.ERROR) as ll: @@ -1601,9 +1526,7 @@ def test_convert_docx_to_pdf_false(self) -> None: "test_files", "owasp_cornucopia_webapp_ver_guide_bridge_lang.docx", ) - want_pdf_filename = os.path.join( - c.convert_vars.BASE_PATH, "tests", "test_files", "test.pdf" - ) + want_pdf_filename = os.path.join(c.convert_vars.BASE_PATH, "tests", "test_files", "test.pdf") # The message varies by platform - Windows/Mac get MS Word suggestion, Linux doesn't base_msg = ( f"WARNING:root:Error. A temporary file {input_docx_filename} was created in the output folder " @@ -1671,36 +1594,24 @@ def test_get_mapping_dict_true(self) -> None: "${VE_VE3_owasp_cre}": "{'owasp_asvs': ['848-711', '743-237', '042-550', '031-447', '532-878', '314-131', '036-725']}", # noqa: E501 } - got_mapping_dict = c.get_mapping_for_edition( - input_yaml_files, "3.0", "en", "webapp", "bridge", "cards" - ) + got_mapping_dict = c.get_mapping_for_edition(input_yaml_files, "3.0", "en", "webapp", "bridge", "cards") self.assertDictEqual(want_mapping_dict, got_mapping_dict) def test_get_mapping_for_edition_empty(self) -> None: - input_yaml_files = [ - os.path.join(self.BASE_PATH, "source", "webapp-cards-3.0-en.yaml") - ] + input_yaml_files = [os.path.join(self.BASE_PATH, "source", "webapp-cards-3.0-en.yaml")] want_mapping_dict: Dict[str, str] = {} with self.assertLogs(logging.getLogger(), logging.WARN) as ll: - got_mapping_dict = c.get_mapping_for_edition( - input_yaml_files, "3.0", "en", "webapp", "bridge", "cards" - ) + got_mapping_dict = c.get_mapping_for_edition(input_yaml_files, "3.0", "en", "webapp", "bridge", "cards") self.assertIn("WARNING:root:No mapping file found", " ".join(ll.output)) self.assertDictEqual(want_mapping_dict, got_mapping_dict) def test_get_mapping_for_edition_wrong_file_type(self) -> None: - input_yaml_files = [ - os.path.join( - self.BASE_PATH, "resources", "originals", "owasp_cornucopia_en.docx" - ) - ] + input_yaml_files = [os.path.join(self.BASE_PATH, "resources", "originals", "owasp_cornucopia_en.docx")] want_mapping_dict: Dict[str, str] = {} with self.assertLogs(logging.getLogger(), logging.WARN) as ll: - got_mapping_dict = c.get_mapping_for_edition( - input_yaml_files, "3.0", "en", "webapp", "bridge", "cards" - ) + got_mapping_dict = c.get_mapping_for_edition(input_yaml_files, "3.0", "en", "webapp", "bridge", "cards") self.assertIn("WARNING:root:No mapping file found", " ".join(ll.output)) self.assertDictEqual(want_mapping_dict, got_mapping_dict) @@ -1733,9 +1644,7 @@ def tearDown(self) -> None: os.remove(self.temp_file) def test_create_edition_from_template_none_valid_input(self) -> None: - self.want_file = os.path.join( - c.convert_vars.BASE_PATH, "output", "owasp_cornucopia_webapp_invalid.docx" - ) + self.want_file = os.path.join(c.convert_vars.BASE_PATH, "output", "owasp_cornucopia_webapp_invalid.docx") if os.path.isfile(self.want_file): os.remove(self.want_file) @@ -1748,18 +1657,14 @@ def test_create_edition_from_template_none_valid_input(self) -> None: ) def test_create_edition_from_template_wrong_base_path(self) -> None: - c.convert_vars.BASE_PATH = ( - os.path.split(os.path.dirname(os.path.realpath(__file__)))[0] - + "invalidpath" - ) + c.convert_vars.BASE_PATH = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0] + "invalidpath" if os.path.isfile(self.want_file): os.remove(self.want_file) with self.assertLogs(logging.getLogger(), logging.ERROR) as l2: c.create_edition_from_template("invalid", "invalid", "invalid", "invalid") self.assertIn( - "ERROR:root:No language files found in folder: " - + str(os.path.join(c.convert_vars.BASE_PATH, "source")), + "ERROR:root:No language files found in folder: " + str(os.path.join(c.convert_vars.BASE_PATH, "source")), " ".join(l2.output), ) @@ -1863,9 +1768,7 @@ def test_create_edition_from_template_spanish(self) -> None: with self.assertLogs(logging.getLogger(), logging.INFO) as ll: c.create_edition_from_template("guide", "es") self.assertIn("INFO:root:New file saved:", " ".join(ll.output)) - self.assertIn( - "owasp_cornucopia_webapp_3.0_guide_bridge_es.odt", " ".join(ll.output) - ) + self.assertIn("owasp_cornucopia_webapp_3.0_guide_bridge_es.odt", " ".join(ll.output)) self.assertTrue(os.path.isfile(want_file)) @@ -1882,18 +1785,12 @@ def test_create_edition_from_template_en_idml(self) -> None: with self.assertLogs(logging.getLogger(), logging.INFO) as ll: c.create_edition_from_template("cards", "en") self.assertIn("INFO:root:New file saved:", " ".join(ll.output)) - self.assertIn( - "owasp_cornucopia_webapp_3.0_cards_bridge_en.idml", " ".join(ll.output) - ) + self.assertIn("owasp_cornucopia_webapp_3.0_cards_bridge_en.idml", " ".join(ll.output)) self.assertTrue(os.path.isfile(self.want_file)) def test_create_edition_from_template_es_specify_output(self) -> None: - c.convert_vars.args.outputfile = os.path.join( - "output", "cornucopia_cards_es.idml" - ) - self.want_file = os.path.join( - c.convert_vars.BASE_PATH, c.convert_vars.args.outputfile - ) + c.convert_vars.args.outputfile = os.path.join("output", "cornucopia_cards_es.idml") + self.want_file = os.path.join(c.convert_vars.BASE_PATH, c.convert_vars.args.outputfile) if os.path.isfile(self.want_file): os.remove(self.want_file) @@ -1978,9 +1875,7 @@ def test_save_docx_file_defaults(self) -> None: "owasp_cornucopia_en_static.docx", ) input_doc = docx.Document(filename) - self.want_file = os.path.join( - c.convert_vars.BASE_PATH, "output", "owasp_cornucopia_en_static.docx" - ) + self.want_file = os.path.join(c.convert_vars.BASE_PATH, "output", "owasp_cornucopia_en_static.docx") if os.path.isfile(self.want_file): os.remove(self.want_file) @@ -2006,9 +1901,7 @@ def setUp(self) -> None: } self.b = c.convert_vars.BASE_PATH c.convert_vars.BASE_PATH = os.path.join(self.b, "tests", "test_files") - self.input_xml_file = os.path.join( - c.convert_vars.BASE_PATH, "output", "temp", "Stories", "Story_u8fb5.xml" - ) + self.input_xml_file = os.path.join(c.convert_vars.BASE_PATH, "output", "temp", "Stories", "Story_u8fb5.xml") if not os.path.exists(os.path.dirname(self.input_xml_file)): os.makedirs(os.path.dirname(self.input_xml_file)) if os.path.isfile(self.input_xml_file): @@ -2058,9 +1951,7 @@ def setUp(self) -> None: } self.b = c.convert_vars.BASE_PATH c.convert_vars.BASE_PATH = os.path.join(self.b, "tests", "test_files") - self.input_xml_file = os.path.join( - c.convert_vars.BASE_PATH, "output", "temp", "Stories", "Story_u8fb5.xml" - ) + self.input_xml_file = os.path.join(c.convert_vars.BASE_PATH, "output", "temp", "Stories", "Story_u8fb5.xml") if not os.path.exists(os.path.dirname(self.input_xml_file)): os.makedirs(os.path.dirname(self.input_xml_file)) if os.path.isfile(self.input_xml_file): @@ -2102,9 +1993,7 @@ def setUp(self) -> None: } self.b = c.convert_vars.BASE_PATH c.convert_vars.BASE_PATH = os.path.join(self.b, "tests", "test_files") - self.input_xml_file = os.path.join( - c.convert_vars.BASE_PATH, "output", "temp", "Stories", "Story_u8fb5.xml" - ) + self.input_xml_file = os.path.join(c.convert_vars.BASE_PATH, "output", "temp", "Stories", "Story_u8fb5.xml") if not os.path.exists(os.path.dirname(self.input_xml_file)): os.makedirs(os.path.dirname(self.input_xml_file)) if os.path.isfile(self.input_xml_file): @@ -2140,9 +2029,7 @@ def setUp(self) -> None: self.input_dict = {} self.b = c.convert_vars.BASE_PATH c.convert_vars.BASE_PATH = os.path.join(self.b, "tests", "test_files") - self.input_xml_file = os.path.join( - c.convert_vars.BASE_PATH, "output", "temp", "Stories", "Story_u8fb5.xml" - ) + self.input_xml_file = os.path.join(c.convert_vars.BASE_PATH, "output", "temp", "Stories", "Story_u8fb5.xml") if not os.path.exists(os.path.dirname(self.input_xml_file)): os.makedirs(os.path.dirname(self.input_xml_file)) if os.path.isfile(self.input_xml_file): @@ -2163,9 +2050,7 @@ def test_replace_text_in_xml_file_fail(self) -> None: """ with self.assertLogs(logging.getLogger(), logging.ERROR) as ll: - c.replace_text_in_xml_file( - self.input_xml_file, list(self.input_dict.items()) - ) + c.replace_text_in_xml_file(self.input_xml_file, list(self.input_dict.items())) self.assertIn( "ERROR:root:Failed to parse XML file", ll.output.pop(), @@ -2194,24 +2079,16 @@ def setUp(self) -> None: def test_get_replacement_value_from_dict_exact(self) -> None: input_text = "${VE_VE2_desc}" - want_data = ( - "You have invented a new attack against Data Validation and Encoding" - ) + want_data = "You have invented a new attack against Data Validation and Encoding" - got_data = c.get_replacement_value_from_dict( - input_text, self.replacement_values - ) + got_data = c.get_replacement_value_from_dict(input_text, self.replacement_values) self.assertEqual(want_data, got_data) def test_get_replacement_value_from_dict_spaced(self) -> None: input_text = " ${VE_VE2_desc} " - want_data = ( - " You have invented a new attack against Data Validation and Encoding " - ) + want_data = " You have invented a new attack against Data Validation and Encoding " - got_data = c.get_replacement_value_from_dict( - input_text, self.replacement_values - ) + got_data = c.get_replacement_value_from_dict(input_text, self.replacement_values) self.assertEqual(want_data, got_data) @@ -2382,9 +2259,7 @@ class TestZipDir(unittest.TestCase): def setUp(self) -> None: self.b = c.convert_vars.BASE_PATH c.convert_vars.BASE_PATH = os.path.join(self.b, "tests", "test_files") - self.input_filename = os.path.join( - c.convert_vars.BASE_PATH, "output", "test.zip" - ) + self.input_filename = os.path.join(c.convert_vars.BASE_PATH, "output", "test.zip") def tearDown(self) -> None: if os.path.isfile(self.input_filename): diff --git a/tests/scripts/smoke_tests.py b/tests/scripts/smoke_tests.py index dcd618245..368dbb9c7 100644 --- a/tests/scripts/smoke_tests.py +++ b/tests/scripts/smoke_tests.py @@ -43,9 +43,7 @@ def test_01_homepage_loads(self) -> None: 200, f"Homepage returned status {response.status_code}", ) - self.assertIn( - "copi", response.text.lower(), "Homepage should contain 'copi' text" - ) + self.assertIn("copi", response.text.lower(), "Homepage should contain 'copi' text") def test_02_cards_route_accessible(self) -> None: """Test that the cards route is accessible""" @@ -62,9 +60,7 @@ def test_03_javascript_loads(self) -> None: response = self._make_request(self.BASE_URL) self.assertEqual(response.status_code, 200) self.assertTrue( - " str: return f"{opencre_rest_url}/id/{cre_id}" -def produce_webapp_mappings( - source_file: Dict[Any, Any], standards_to_add: list[str] -) -> Dict[Any, Any]: +def produce_webapp_mappings(source_file: Dict[Any, Any], standards_to_add: list[str]) -> Dict[Any, Any]: base = { "meta": { "edition": "webapp", @@ -42,8 +40,8 @@ def produce_webapp_mappings( for standard in standards_to_add: for link in cre_object.get("links"): if link.get("document").get("name") == standard: - source_file["suits"][indx]["cards"][card_indx][standard] = ( - link.get("document").get("sectionID") + source_file["suits"][indx]["cards"][card_indx][standard] = link.get("document").get( + "sectionID" ) else: print(f"could not find CRE {cre}, status code {response.status_code}") @@ -72,9 +70,7 @@ def main() -> None: help="Where to find the file mapping cornucopia to CREs", required=True, ) - parser.add_argument( - "-t", "--target", help="Path where to store the result", required=True - ) + parser.add_argument("-t", "--target", help="Path where to store the result", required=True) parser.add_argument( "-s", "--staging",