diff --git a/cli/completer.go b/cli/completer.go index 6db3d30..73155db 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) + } +}