diff --git a/pyxform/validators/pyxform/android_package_name.py b/pyxform/validators/pyxform/android_package_name.py new file mode 100644 index 00000000..aa4c8716 --- /dev/null +++ b/pyxform/validators/pyxform/android_package_name.py @@ -0,0 +1,31 @@ +import re + + +def validate_android_package_name(name: str) -> str | None: + prefix = "Invalid Android package name - " + + if not name.strip(): + return f"{prefix}package name is missing." + + if "." not in name: + return f"{prefix}the package name must have at least one '.' separator." + + if name[-1] == ".": + return f"{prefix}the package name cannot end in a '.' separator." + + segments = name.split(".") + if any(segment == "" for segment in segments): + return f"{prefix}package segments must be of non-zero length." + + if any(segment.startswith("_") for segment in segments): + return f"{prefix}the character '_' cannot be the first character in a package name segment." + + if any(segment[0].isdigit() for segment in segments): + return f"{prefix}a digit cannot be the first character in a package name segment." + + pattern = re.compile(r"[^a-zA-Z0-9._]") + for segment in segments: + if pattern.search(segment): + return f"{prefix}the package name contains not allowed characters." + + return None diff --git a/pyxform/xls2json.py b/pyxform/xls2json.py index 771f815d..b97e4f88 100644 --- a/pyxform/xls2json.py +++ b/pyxform/xls2json.py @@ -24,6 +24,7 @@ from pyxform.errors import PyXFormError from pyxform.utils import PYXFORM_REFERENCE_REGEX, default_is_dynamic from pyxform.validators.pyxform import parameters_generic, select_from_file_params +from pyxform.validators.pyxform.android_package_name import validate_android_package_name from pyxform.validators.pyxform.translations_checks import SheetTranslations from pyxform.xls2json_backends import csv_to_dict, xls_to_dict, xlsx_to_dict from pyxform.xlsparseutils import find_sheet_misspellings, is_valid_xml_tag @@ -1334,17 +1335,15 @@ def workbook_to_json( if "app" in parameters.keys(): appearance = row.get("control", {}).get("appearance") if appearance is None or appearance == "annotate": - android_package_regex_pattern = ( - "^[a-z][a-z0-9_]*(\.[a-z0-9_]+)+[0-9a-z_]$" - ) app_package_name = str(parameters["app"]) - if re.fullmatch(android_package_regex_pattern, app_package_name): + validation_result = validate_android_package_name(app_package_name) + if validation_result is None: new_dict["control"] = new_dict.get("control", {}) new_dict["control"].update({"intent": app_package_name}) else: + aaa = (ROW_FORMAT_STRING % row_number) + " " + validation_result raise PyXFormError( - (ROW_FORMAT_STRING % row_number) - + " Invalid Android package name format." + (ROW_FORMAT_STRING % row_number) + " " + validation_result ) parent_children_array.append(new_dict) diff --git a/tests/test_image_app_parameter.py b/tests/test_image_app_parameter.py index 9f87c49c..a884ff9b 100644 --- a/tests/test_image_app_parameter.py +++ b/tests/test_image_app_parameter.py @@ -4,6 +4,7 @@ """ from tests.pyxform_test_case import PyxformTestCase + class TestImageAppParameter(PyxformTestCase): def test_adding_valid_android_package_name_in_image_with_supported_appearances(self): appearances = ("", "annotate") @@ -22,19 +23,40 @@ def test_adding_valid_android_package_name_in_image_with_supported_appearances(s ], ) - def test_throwing_error_when_invalid_android_package_name_is_used(self): + def test_throwing_error_when_invalid_android_package_name_is_used_with_supported_appearances( + self, + ): appearances = ("", "annotate") md = """ | survey | | | | | | | | type | name | label | parameters | appearance | | | image | my_image | Image | app=something | {case} | """ - error__contains = (["[row = 2] Invalid Android package name format"],) for case in appearances: with self.subTest(msg=case): self.assertPyxformXform( name="data", errored=True, + error__contains="[row : 2] Invalid Android package name - the package name must have at least one '.' separator.", + md=md.format(case=case), + xml__xpath_match=[ + "/h:html/h:body/x:upload[not(@intent) and @mediatype='image/*' and @ref='/data/my_image']" + ], + ) + + def test_ignoring_invalid_android_package_name_is_used_with_not_supported_appearances( + self, + ): + appearances = ("signature", "draw", "new-front") + md = """ + | survey | | | | | | + | | type | name | label | parameters | appearance | + | | image | my_image | Image | app=something | {case} | + """ + for case in appearances: + with self.subTest(msg=case): + self.assertPyxformXform( + name="data", md=md.format(case=case), xml__xpath_match=[ "/h:html/h:body/x:upload[not(@intent) and @mediatype='image/*' and @ref='/data/my_image']" diff --git a/tests/validators/pyxform/test_android_package_name.py b/tests/validators/pyxform/test_android_package_name.py new file mode 100644 index 00000000..0b7e402b --- /dev/null +++ b/tests/validators/pyxform/test_android_package_name.py @@ -0,0 +1,63 @@ +from pyxform.validators.pyxform.android_package_name import validate_android_package_name +from tests.pyxform_test_case import PyxformTestCase + + +class TestAndroidPackageNameValidator(PyxformTestCase): + def test_empty_package_name(self): + result = validate_android_package_name("") + self.assertEqual( + result, "Invalid Android package name - package name is missing." + ) + + def test_blank_package_name(self): + result = validate_android_package_name(" ") + self.assertEqual( + result, "Invalid Android package name - package name is missing." + ) + + def test_missing_separator(self): + result = validate_android_package_name("comexampleapp") + self.assertEqual( + result, + "Invalid Android package name - the package name must have at least one '.' separator.", + ) + + def test_invalid_start_with_underscore(self): + result = validate_android_package_name("_com.example.app") + expected_error = "Invalid Android package name - the character '_' cannot be the first character in a package name segment." + self.assertEqual(result, expected_error) + + def test_invalid_start_with_digit(self): + result = validate_android_package_name("1com.example.app") + expected_error = "Invalid Android package name - a digit cannot be the first character in a package name segment." + self.assertEqual(result, expected_error) + + def test_invalid_character(self): + result = validate_android_package_name("com.example.app$") + expected_error = "Invalid Android package name - the package name contains not allowed characters." + self.assertEqual(result, expected_error) + + def test_package_name_segment_with_zero_length(self): + result = validate_android_package_name("com..app") + expected_error = ( + "Invalid Android package name - package segments must be of non-zero length." + ) + self.assertEqual(result, expected_error) + + def test_separator_as_last_char_in_package_name(self): + result = validate_android_package_name("com.example.app.") + expected_error = "Invalid Android package name - the package name cannot end in a '.' separator." + self.assertEqual(result, expected_error) + + def test_valid_package_name(self): + package_names = ( + "com.zenstudios.zenpinball", + "com.outfit7.talkingtom", + "com.zeptolab.ctr2.f2p.google", + "com.ea.game.pvzfree_row", + "com.rovio.angrybirdsspace.premium", + ) + + for case in package_names: + result = validate_android_package_name(case) + self.assertIsNone(result)