This is an automated email from the ASF dual-hosted git repository.

borisstoyanov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack-cloudmonkey.git


The following commit(s) were added to refs/heads/main by this push:
     new 9a04d5b  Use Related metadata for autocomplete API discovery (#214)
9a04d5b is described below

commit 9a04d5bea6ede074afe48cdfb2b9ccf398fa4d5d
Author: Dheeraj Bansal <[email protected]>
AuthorDate: Wed Sep 9 13:08:08 2026 +0530

    Use Related metadata for autocomplete API discovery (#214)
    
    * feat: use Related metadata for autocomplete API discovery
    
    * test: add autocomplete API discovery coverage
    
    * Use Related metadata only when the noun heuristics find nothing
    
    Checking Related before the heuristics changed 47 existing completions
    against the bundled API cache, many of them wrong: registerIso projectid
    resolved to listProjectAccounts instead of listProjects, acquirePodIpAddress
    podid to listZones instead of listPods, and executeWebhookDelivery webhookid
    to listWebhookDeliveries instead of listWebhooks. The loose Related match
    picks the first list API in the array, whatever its noun.
    
    Move the Related lookup after the noun heuristics and run it only when they
    found no API. That keeps all 142 new completions Related metadata adds and
    leaves every existing completion untouched. Add a test pinning the ordering.
    
    ---------
    
    Co-authored-by: Boris Stoyanov <[email protected]>
---
 cli/completer.go      |  16 ++++
 cli/completer_test.go | 206 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 222 insertions(+)

diff --git a/cli/completer.go b/cli/completer.go
index 55d8207..9547f5a 100644
--- a/cli/completer.go
+++ b/cli/completer.go
@@ -287,6 +287,22 @@ func findAutocompleteAPI(arg *config.APIArg, apiFound 
*config.API, apiMap map[st
                relatedNoun = relatedNoun[:len(relatedNoun)-1]
        }
 
+       // Prefer the API's own Related metadata when the noun heuristics found
+       // nothing, so entity-reference args still get completions.
+       if autocompleteAPI == nil {
+               for _, relatedAPI := range arg.Related {
+                       if !strings.HasPrefix(strings.ToLower(relatedAPI), 
"list") {
+                               continue
+                       }
+                       for _, listAPI := range apiMap["list"] {
+                               if strings.EqualFold(listAPI.Name, relatedAPI) {
+                                       config.Debug("Autocomplete: API found 
using Related metadata: ", listAPI.Name)
+                                       return listAPI
+                               }
+                       }
+               }
+       }
+
        // Heuristic: find any list API that contains the arg name
        if autocompleteAPI == nil {
                config.Debug("Finding possible API that have: ", argName, " 
related APIs: ", arg.Related)
diff --git a/cli/completer_test.go b/cli/completer_test.go
new file mode 100644
index 0000000..aea7836
--- /dev/null
+++ b/cli/completer_test.go
@@ -0,0 +1,206 @@
+package cli
+
+import (
+       "testing"
+
+       "github.com/apache/cloudstack-cloudmonkey/config"
+)
+
+func TestFindAutocompleteAPIRelatedNounMatch(t *testing.T) {
+       arg := &config.APIArg{
+               Name: "domainid=",
+               Related: []string{
+                       "createDomain",
+                       "listDomains",
+                       "updateDomain",
+               },
+       }
+
+       apiFound := &config.API{
+               Name: "listVirtualMachines",
+               Verb: "list",
+               Noun: "virtualmachines",
+       }
+
+       apiMap := map[string][]*config.API{
+               "list": {
+                       {
+                               Name: "listDomains",
+                               Noun: "domains",
+                       },
+               },
+       }
+
+       result := findAutocompleteAPI(arg, apiFound, apiMap)
+
+       if result == nil {
+               t.Fatal("expected API, got nil")
+       }
+
+       if result.Name != "listDomains" {
+               t.Fatalf("expected listDomains, got %s", result.Name)
+       }
+}
+
+func TestFindAutocompleteAPIRelatedFallback(t *testing.T) {
+       arg := &config.APIArg{
+               Name: "domainid=",
+               Related: []string{
+                       "listDomainChildren",
+               },
+       }
+
+       apiFound := &config.API{
+               Name: "listVirtualMachines",
+               Verb: "list",
+               Noun: "virtualmachines",
+       }
+
+       apiMap := map[string][]*config.API{
+               "list": {
+                       {
+                               Name: "listDomainChildren",
+                               Noun: "domainchildren",
+                       },
+               },
+       }
+
+       result := findAutocompleteAPI(arg, apiFound, apiMap)
+
+       if result == nil {
+               t.Fatal("expected API, got nil")
+       }
+
+       if result.Name != "listDomainChildren" {
+               t.Fatalf("expected listDomainChildren, got %s", result.Name)
+       }
+}
+
+func TestFindAutocompleteAPIEmptyRelatedFallsBackToHeuristic(t *testing.T) {
+       arg := &config.APIArg{
+               Name: "zoneid=",
+       }
+
+       apiFound := &config.API{
+               Name: "listVirtualMachines",
+               Verb: "list",
+               Noun: "virtualmachines",
+       }
+
+       apiMap := map[string][]*config.API{
+               "list": {
+                       {
+                               Name: "listZones",
+                               Noun: "zones",
+                       },
+               },
+       }
+
+       result := findAutocompleteAPI(arg, apiFound, apiMap)
+
+       if result == nil {
+               t.Fatal("expected API, got nil")
+       }
+
+       if result.Name != "listZones" {
+               t.Fatalf("expected listZones, got %s", result.Name)
+       }
+}
+
+func TestFindAutocompleteAPINonListRelatedFallsBackToHeuristic(t *testing.T) {
+       arg := &config.APIArg{
+               Name: "zoneid=",
+               Related: []string{
+                       "createZone",
+                       "updateZone",
+               },
+       }
+
+       apiFound := &config.API{
+               Name: "listVirtualMachines",
+               Verb: "list",
+               Noun: "virtualmachines",
+       }
+
+       apiMap := map[string][]*config.API{
+               "list": {
+                       {
+                               Name: "listZones",
+                               Noun: "zones",
+                       },
+               },
+       }
+
+       result := findAutocompleteAPI(arg, apiFound, apiMap)
+
+       if result == nil {
+               t.Fatal("expected API, got nil")
+       }
+
+       if result.Name != "listZones" {
+               t.Fatalf("expected listZones, got %s", result.Name)
+       }
+}
+
+func TestFindAutocompleteAPIMapTypeReturnsNil(t *testing.T) {
+       arg := &config.APIArg{
+               Type: "map",
+       }
+
+       apiFound := &config.API{
+               Name: "listVirtualMachines",
+               Verb: "list",
+               Noun: "virtualmachines",
+       }
+
+       apiMap := map[string][]*config.API{
+               "list": {},
+       }
+
+       result := findAutocompleteAPI(arg, apiFound, apiMap)
+
+       if result != nil {
+               t.Fatalf("expected nil, got %v", result)
+       }
+}
+
+func TestFindAutocompleteAPIHeuristicWinsOverRelated(t *testing.T) {
+       // registerIso's projectid arg lists many related APIs; the noun 
heuristic
+       // must keep winning so the completion stays listProjects.
+       arg := &config.APIArg{
+               Name: "projectid=",
+               Related: []string{
+                       "listProjectAccounts",
+                       "listProjects",
+               },
+       }
+
+       apiFound := &config.API{
+               Name: "registerIso",
+               Verb: "register",
+               Noun: "iso",
+       }
+
+       apiMap := map[string][]*config.API{
+               "list": {
+                       {
+                               Name: "listProjectAccounts",
+                               Noun: "projectaccounts",
+                       },
+                       {
+                               Name: "listProjects",
+                               Noun: "projects",
+                       },
+               },
+       }
+
+       result := findAutocompleteAPI(arg, apiFound, apiMap)
+
+       if result == nil {
+               t.Fatal("expected API, got nil")
+       }
+
+       if result.Name != "listProjects" {
+               t.Fatalf("expected listProjects, got %s", result.Name)
+       }
+}

Reply via email to