diff --git a/src/aaz_dev/swagger/controller/command_generator.py b/src/aaz_dev/swagger/controller/command_generator.py index 4c418681..f8400d7e 100644 --- a/src/aaz_dev/swagger/controller/command_generator.py +++ b/src/aaz_dev/swagger/controller/command_generator.py @@ -207,6 +207,13 @@ def generate_command_group_name_by_resource(cls, resource_path, rp_name): part = re.sub(r"[^a-zA-Z0-9\-._]", '', part) name = camel_case_to_snake_case(part, '-') singular_name = to_singular(name) or name + # to_singular can strip a trailing acronym token (e.g. "saa-s" -> "saa-"), + # leaving a dangling separator and an unusable command name. Merge the + # stripped token back into the preceding one in that case + # (e.g. "activate-saa-s" -> "activate-saas"). + if singular_name.endswith('-'): + head, _, last_token = name.rpartition('-') + singular_name = head + last_token names.append(singular_name) return " ".join([name for name in names if name]) diff --git a/src/aaz_dev/swagger/tests/controller_tests/test_command_generator.py b/src/aaz_dev/swagger/tests/controller_tests/test_command_generator.py index ffdb1254..caa5e021 100644 --- a/src/aaz_dev/swagger/tests/controller_tests/test_command_generator.py +++ b/src/aaz_dev/swagger/tests/controller_tests/test_command_generator.py @@ -3,6 +3,7 @@ from swagger.model.specs._utils import get_url_path_valid_parts from swagger.utils import exceptions from command.model.configuration import CMDBuildInVariants +import unittest MUTE_ERROR_MESSAGES = ( @@ -280,3 +281,44 @@ def test_command_group_name_data_plane(self): if name in command_group_names and command_group_names[name][0] != valid_url: print(f"Duplicated command group name : '{name}' :\n\t{command_group_names[name][0]} and {valid_url} :\n\t\t{command_group_names[name][1]}\n\t\t{resource.path}") command_group_names[name] = (valid_url, resource.path) + + +class CommandGroupNameUnitTestCase(unittest.TestCase): + """Pure-string tests for command group name generation (no swagger specs required).""" + + def _gen(self, resource_path, rp_name): + return SwaggerCommandGenerator.generate_command_group_name_by_resource( + resource_path=resource_path, rp_name=rp_name) + + def test_trailing_acronym_does_not_break_name(self): + # Abbreviations ending in a capital letter (e.g., "SaaS") will be tokenized as "saa-s"; + # `to_singular` will remove the trailing "s", leaving a null separator + # ("activate-saa-"). The generator must merge the stripped token back + # ("activate-saas") to keep the name usable. + rp = "Napster.CompanionAPI" + cases = { + "/subscriptions/{subscriptionId}/providers/Napster.CompanionAPI/activateSaaS": + "napster companion-api activate-saas", + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/" + "Napster.CompanionAPI/organizations/{organizationname}/linkSaaS": + "napster companion-api organization link-saas", + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/" + "Napster.CompanionAPI/organizations/{organizationname}/latestLinkedSaaS": + "napster companion-api organization latest-linked-saas", + } + for path, expected in cases.items(): + name = self._gen(path, rp) + self.assertEqual(name, expected) + self.assertFalse(name.endswith('-'), f"dangling separator in: {name!r}") + + def test_normal_plurals_still_singularized(self): + # Regression guard: ordinary plural segments must still be singularized. + self.assertEqual( + self._gen("/subscriptions/{subscriptionId}/providers/Microsoft.Compute/virtualMachines", + "Microsoft.Compute"), + "compute virtual-machine") + self.assertEqual( + self._gen("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/" + "Napster.CompanionAPI/organizations/{organizationname}", + "Napster.CompanionAPI"), + "napster companion-api organization")