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 diff --git a/scripts/check_translations.py b/scripts/check_translations.py index 6094de1cb..1cffbad58 100644 --- a/scripts/check_translations.py +++ b/scripts/check_translations.py @@ -214,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 d23d83297..201c9a6e5 100644 --- a/scripts/check_translations_itest.py +++ b/scripts/check_translations_itest.py @@ -82,7 +82,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 +99,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..89cb6575c 100644 --- a/scripts/check_translations_utest.py +++ b/scripts/check_translations_utest.py @@ -131,7 +131,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"} @@ -147,11 +151,18 @@ 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) @@ -176,9 +187,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,7 +226,11 @@ 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") @@ -213,9 +240,17 @@ def test_portuguese_brazil_untranslated_detected(self) -> None: 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.""" @@ -230,7 +265,11 @@ def test_lang_names_in_report(self) -> None: 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( + "Portuguese (Brazil)", + report, + "Portuguese (Brazil) display name should appear for pt_br", + ) class TestCoverageBranches(unittest.TestCase): @@ -262,7 +301,9 @@ def test_get_file_groups_skips_archive_files(self) -> None: 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: diff --git a/scripts/convert.py b/scripts/convert.py index 3f60754fc..767632107 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,7 +47,11 @@ 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"]) args: argparse.Namespace @@ -113,7 +129,24 @@ def _safe_extractall(archive: zipfile.ZipFile, target_dir: str) -> None: 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}") @@ -163,7 +196,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,7 +283,11 @@ 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 @@ -308,7 +349,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} " @@ -389,7 +437,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,10 +619,15 @@ 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 @@ -597,7 +652,12 @@ 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) if not mapping_data: @@ -674,7 +734,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 +754,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] @@ -759,7 +829,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 ( @@ -1023,7 +1096,10 @@ def save_idml_file(template_doc: str, language_dict: Dict[str, str], output_file # 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) @@ -1060,7 +1136,9 @@ 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) diff --git a/scripts/convert_asvs.py b/scripts/convert_asvs.py index eff5868fd..0867f26e6 100644 --- a/scripts/convert_asvs.py +++ b/scripts/convert_asvs.py @@ -30,7 +30,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: @@ -112,7 +116,10 @@ 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") @@ -163,7 +170,10 @@ def _process_requirement_item( 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]] = [] @@ -176,7 +186,16 @@ def create_asvs_pages( 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) diff --git a/scripts/convert_capec.py b/scripts/convert_capec.py index 92d4717e3..479fa3498 100644 --- a/scripts/convert_capec.py +++ b/scripts/convert_capec.py @@ -95,7 +95,7 @@ def parse_description(description_field: Any) -> str: 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) diff --git a/scripts/convert_capec_map_to_asvs_map.py b/scripts/convert_capec_map_to_asvs_map.py index d85f35597..60279b269 100644 --- a/scripts/convert_capec_map_to_asvs_map.py +++ b/scripts/convert_capec_map_to_asvs_map.py @@ -364,14 +364,20 @@ def main() -> None: 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_utest.py b/tests/scripts/capec_map_enricher_utest.py index 748a63780..57bb4b420 100644 --- a/tests/scripts/capec_map_enricher_utest.py +++ b/tests/scripts/capec_map_enricher_utest.py @@ -234,7 +234,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""" @@ -379,7 +383,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..4052d88b6 100644 --- a/tests/scripts/convert_asvs_itest.py +++ b/tests/scripts/convert_asvs_itest.py @@ -143,7 +143,11 @@ def test_create_asvs_pages_integration(self): # 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)") + 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]: @@ -383,7 +387,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..170f95430 100644 --- a/tests/scripts/convert_asvs_utest.py +++ b/tests/scripts/convert_asvs_utest.py @@ -369,7 +369,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""" diff --git a/tests/scripts/convert_capec_itest.py b/tests/scripts/convert_capec_itest.py index f318e465f..5063f451a 100644 --- a/tests/scripts/convert_capec_itest.py +++ b/tests/scripts/convert_capec_itest.py @@ -257,7 +257,11 @@ def test_description_parsing_variations(self): 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""" 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..704f9015c 100644 --- a/tests/scripts/convert_capec_map_to_asvs_map_itest.py +++ b/tests/scripts/convert_capec_map_to_asvs_map_itest.py @@ -104,7 +104,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) @@ -194,7 +197,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,7 +214,9 @@ 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): 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..079b8c3da 100644 --- a/tests/scripts/convert_capec_map_to_asvs_map_utest.py +++ b/tests/scripts/convert_capec_map_to_asvs_map_utest.py @@ -56,7 +56,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) @@ -117,7 +126,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) @@ -734,7 +752,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..e6f9bddae 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") @@ -318,7 +328,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": { @@ -366,7 +380,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 +399,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": { @@ -535,7 +560,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 +611,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"}], + }, ], } ] @@ -756,7 +788,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 +828,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"}], + }, ], } ] diff --git a/tests/scripts/convert_fuzzer.py b/tests/scripts/convert_fuzzer.py index 95ae27c24..36131486c 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, @@ -58,7 +69,20 @@ def test_main(data): 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, @@ -76,7 +100,20 @@ def test_main(data): 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, @@ -94,7 +131,20 @@ def test_main(data): 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, @@ -112,7 +162,20 @@ def test_main(data): 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, @@ -130,7 +193,20 @@ def test_main(data): 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, @@ -181,7 +257,20 @@ def test_main(data): 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, diff --git a/tests/scripts/convert_itest.py b/tests/scripts/convert_itest.py index c45901fe8..c8a9f78ce 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, diff --git a/tests/scripts/convert_utest.py b/tests/scripts/convert_utest.py index de2614c85..3fc4966dc 100644 --- a/tests/scripts/convert_utest.py +++ b/tests/scripts/convert_utest.py @@ -320,7 +320,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 +336,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 +351,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 +373,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) @@ -390,7 +411,12 @@ 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"} + self.input_meta_data = { + "edition": "webapp", + "component": "cards", + "language": "EN", + "version": "3.0", + } def tearDown(self) -> None: c.convert_vars.args.outputfile = "" @@ -421,7 +447,9 @@ 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) @@ -433,7 +461,9 @@ 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) @@ -445,7 +475,9 @@ 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) @@ -461,7 +493,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 @@ -497,7 +534,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 +563,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) @@ -544,7 +591,12 @@ def setUp(self) -> None: 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 +643,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"]) @@ -692,7 +749,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 +839,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,12 +864,31 @@ 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", + ] }, } @@ -805,7 +906,12 @@ def setUp(self) -> None: 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,7 +952,12 @@ 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) self.assertEqual(want_meta, got_data["meta"]) @@ -948,7 +1059,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 +1149,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,12 +1174,31 @@ 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", + ] }, } @@ -1149,7 +1304,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"]) @@ -1204,7 +1365,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 +1386,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 +1420,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 +1432,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,7 +1521,10 @@ 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") # The message varies by platform - Windows/Mac get MS Word suggestion, Linux doesn't @@ -1438,7 +1619,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 @@ -1482,7 +1668,9 @@ def test_create_edition_from_template_wrong_base_path(self) -> None: " ".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 +1712,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,7 +1757,11 @@ 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) @@ -1580,7 +1774,9 @@ def test_create_edition_from_template_spanish(self) -> None: 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): @@ -1643,7 +1839,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,7 +1868,12 @@ 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") if os.path.isfile(self.want_file): @@ -1848,7 +2051,11 @@ 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.") + 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,9 +2066,15 @@ 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: @@ -1881,21 +2094,54 @@ def test_get_replacement_value_from_dict_spaced(self) -> None: 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 +2150,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) @@ -2047,7 +2326,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 +2343,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 +2361,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 +2388,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 +2401,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 +2445,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..368dbb9c7 100644 --- a/tests/scripts/smoke_tests.py +++ b/tests/scripts/smoke_tests.py @@ -38,14 +38,22 @@ 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.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""" diff --git a/tests/test_files/gen_mappings.py b/tests/test_files/gen_mappings.py index 8253689fd..f9deff1ee 100644 --- a/tests/test_files/gen_mappings.py +++ b/tests/test_files/gen_mappings.py @@ -24,7 +24,12 @@ def make_cre_link(cre_id: str, frontend: bool = False) -> str: 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"]): @@ -59,13 +64,23 @@ 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( + "-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" + "-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" + "-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"]: