From 23fcbd91a16983c226d002d5114964be06d4425d Mon Sep 17 00:00:00 2001 From: Jian Hui Date: Fri, 19 Jun 2026 09:34:22 +0800 Subject: [PATCH 1/2] fix: dangling separator and an unusable command name in command generator --- .../swagger/controller/command_generator.py | 5 +++ .../test_command_generator.py | 41 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/aaz_dev/swagger/controller/command_generator.py b/src/aaz_dev/swagger/controller/command_generator.py index 4c418681..43f86537 100644 --- a/src/aaz_dev/swagger/controller/command_generator.py +++ b/src/aaz_dev/swagger/controller/command_generator.py @@ -207,6 +207,11 @@ 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. Keep the + # un-singularized name in that case. + if singular_name.endswith('-'): + singular_name = name 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..a12dee35 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,43 @@ 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 keep the name usable instead. + rp = "Napster.CompanionAPI" + cases = { + "/subscriptions/{subscriptionId}/providers/Napster.CompanionAPI/activateSaaS": + "napster companion-api activate-saa-s", + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/" + "Napster.CompanionAPI/organizations/{organizationname}/linkSaaS": + "napster companion-api organization link-saa-s", + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/" + "Napster.CompanionAPI/organizations/{organizationname}/latestLinkedSaaS": + "napster companion-api organization latest-linked-saa-s", + } + 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") From 3260a6a7221b7873a674c18bb52783a0ed6c46e0 Mon Sep 17 00:00:00 2001 From: Jian Hui Date: Fri, 19 Jun 2026 10:26:44 +0800 Subject: [PATCH 2/2] fix: merge the stripped token back --- src/aaz_dev/swagger/controller/command_generator.py | 8 +++++--- .../tests/controller_tests/test_command_generator.py | 9 +++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/aaz_dev/swagger/controller/command_generator.py b/src/aaz_dev/swagger/controller/command_generator.py index 43f86537..f8400d7e 100644 --- a/src/aaz_dev/swagger/controller/command_generator.py +++ b/src/aaz_dev/swagger/controller/command_generator.py @@ -208,10 +208,12 @@ def generate_command_group_name_by_resource(cls, resource_path, rp_name): 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. Keep the - # un-singularized name in that case. + # 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('-'): - singular_name = name + 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 a12dee35..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 @@ -293,17 +293,18 @@ def _gen(self, resource_path, 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 keep the name usable instead. + # ("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-saa-s", + "napster companion-api activate-saas", "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/" "Napster.CompanionAPI/organizations/{organizationname}/linkSaaS": - "napster companion-api organization link-saa-s", + "napster companion-api organization link-saas", "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/" "Napster.CompanionAPI/organizations/{organizationname}/latestLinkedSaaS": - "napster companion-api organization latest-linked-saa-s", + "napster companion-api organization latest-linked-saas", } for path, expected in cases.items(): name = self._gen(path, rp)