mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 11:01:25 +01:00
Every value completed to file names, because no command registered a completion. Values without a completer now complete nothing, and only path flags (--*-file, --input, --attachment, --asset, --file, --ssh-key, --dir, --cwd) and arguments named file or directory complete paths. --json completes the fields of the command, also after a comma. --host, --user, --remote and -R complete from hosts.yml, Git remotes and the instance. enumFlag and enumValues give a flag one list of values that validation (case-insensitive) and completion share, and instance completers for issues, pull requests, labels, milestones, users, branches, tags, releases, organizations, teams, workflows, runs, secrets and variables are ready for commands to attach. They give up after two seconds, never prompt, retry or refresh an OAuth token, and drop values with control characters. tea completion bash now says to use eval "$(tea completion bash)", because bash 3.2 on macOS cannot source <(...), and names the bash-completion package; the zsh help says to run compinit first. The README matches. Signed-off-by: bircni <bircni@icloud.com>
635 lines
22 KiB
Go
635 lines
22 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
"unicode"
|
|
|
|
"gitea.dev/tea/internal/clierr"
|
|
"github.com/spf13/cobra"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestHelpWorksWithoutValidConfiguration(t *testing.T) {
|
|
for _, args := range [][]string{{}, {"help"}, {"version"}, {"man"}, {"man", "pr", "checkout"}, {"completion", "bash"}, {"--help"}} {
|
|
deps, out, _ := testDeps(t)
|
|
require.NoError(t, os.WriteFile(filepath.Join(deps.Settings.Dir, "config.yml"), []byte("not: ["), 0o600))
|
|
require.NoError(t, Execute(context.Background(), deps, args))
|
|
require.NotEmpty(t, out.String())
|
|
}
|
|
}
|
|
|
|
func TestAliasesAndConfigPersistence(t *testing.T) {
|
|
deps, out, _ := testDeps(t)
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"alias", "set", "prefs", "config list"}))
|
|
out.Reset()
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"prefs"}))
|
|
require.Contains(t, out.String(), "git_protocol")
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"alias", "set", "preferences", "prefs"}))
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"config", "set", "prompt", "disabled"}))
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"alias", "delete", "preferences"}))
|
|
err := Execute(context.Background(), deps, []string{"alias", "set", "config", "issue list"})
|
|
require.Error(t, err)
|
|
require.Equal(t, clierr.Usage, clierr.ExitCode(err))
|
|
s, err := deps.Settings.Load()
|
|
require.NoError(t, err)
|
|
require.Equal(t, "disabled", s.Preferences.Prompt)
|
|
}
|
|
|
|
func TestBrokenAliasDoesNotBlockOtherCommands(t *testing.T) {
|
|
deps, out, _ := testDeps(t)
|
|
require.NoError(t, os.WriteFile(filepath.Join(deps.Settings.Dir, "config.yml"), []byte("version: 1\naliases:\n status: config list\n dangling: missing\n"), 0o600))
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"config", "list"}))
|
|
require.Contains(t, out.String(), "git_protocol")
|
|
err := Execute(context.Background(), deps, []string{"dangling"})
|
|
require.Equal(t, clierr.Usage, clierr.ExitCode(err))
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"alias", "delete", "status"}))
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"alias", "delete", "dangling"}))
|
|
}
|
|
|
|
func TestCommandLineEdgeCases(t *testing.T) {
|
|
deps, out, _ := testDeps(t)
|
|
err := Execute(context.Background(), deps, []string{"issue", "bogus"})
|
|
require.Equal(t, clierr.Usage, clierr.ExitCode(err))
|
|
err = Execute(context.Background(), deps, []string{"isue"})
|
|
require.Equal(t, clierr.Usage, clierr.ExitCode(err))
|
|
require.ErrorContains(t, err, "did you mean issue?")
|
|
// A help flag supplied as a flag value must not bypass validation.
|
|
err = Execute(context.Background(), deps, []string{"config", "list", "--jq", ".[", "--remote", "-h"})
|
|
require.ErrorContains(t, err, "invalid --jq expression")
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"--version"}))
|
|
require.NotContains(t, out.String(), "tea version")
|
|
}
|
|
|
|
func TestAliasesAfterGlobalFlagsAndNestedHelp(t *testing.T) {
|
|
deps, out, _ := testDeps(t)
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"alias", "set", "prefs", "config list"}))
|
|
out.Reset()
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"--no-input", "prefs"}))
|
|
require.Contains(t, out.String(), "git_protocol")
|
|
require.NoError(t, os.WriteFile(filepath.Join(deps.Settings.Dir, "config.yml"), []byte("broken: ["), 0o600))
|
|
for _, args := range [][]string{{"issue", "--help"}, {"repo"}, {"auth", "login", "--help"}} {
|
|
require.NoError(t, Execute(context.Background(), deps, args))
|
|
}
|
|
}
|
|
|
|
func TestJoinJSONFields(t *testing.T) {
|
|
for _, tc := range []struct{ args, want []string }{
|
|
{[]string{"issue", "list", "--json", "number,title"}, []string{"issue", "list", "--json=number,title"}},
|
|
{[]string{"--json", "id", "org", "list"}, []string{"--json=id", "org", "list"}},
|
|
{[]string{"issue", "list", "--json"}, []string{"issue", "list", "--json"}},
|
|
{[]string{"search", "repo", "foo", "--json"}, []string{"search", "repo", "foo", "--json"}},
|
|
{[]string{"issue", "view", "--json", "5"}, []string{"issue", "view", "--json", "5"}},
|
|
{[]string{"repo", "clone", "--json", "alice/project"}, []string{"repo", "clone", "--json", "alice/project"}},
|
|
{[]string{"issue", "list", "--json", "--state", "open"}, []string{"issue", "list", "--json", "--state", "open"}},
|
|
{[]string{"search", "issues", "--", "--json", "id"}, []string{"search", "issues", "--", "--json", "id"}},
|
|
{[]string{"search", "repo", "--json", "tea"}, []string{"search", "repo", "--json", "tea"}},
|
|
{[]string{"search", "issue", "--json", "bug"}, []string{"search", "issue", "--json", "bug"}},
|
|
{[]string{"repo", "clone", "--json", "project"}, []string{"repo", "clone", "--json", "project"}},
|
|
{[]string{"search", "repo", "--json", "name,url", "tea"}, []string{"search", "repo", "--json=name,url", "tea"}},
|
|
{[]string{"--json", "number", "my-alias"}, []string{"--json=number", "my-alias"}},
|
|
// A word that cannot be an argument is a misspelled field, which then lists the available ones.
|
|
{[]string{"issue", "list", "--json", "Number"}, []string{"issue", "list", "--json=Number"}},
|
|
{[]string{"issue", "list", "--json", "number,titel"}, []string{"issue", "list", "--json=number,titel"}},
|
|
{[]string{"run", "list", "--json", "workflow", "--limit", "5"}, []string{"run", "list", "--json=workflow", "--limit", "5"}},
|
|
{[]string{"issue", "view", "--json", "titel"}, []string{"issue", "view", "--json", "titel"}},
|
|
{[]string{"issue", "view", "5", "--json", "titel"}, []string{"issue", "view", "5", "--json=titel"}},
|
|
} {
|
|
deps, _, _ := testDeps(t)
|
|
require.Equal(t, tc.want, joinJSONFields(NewRoot(deps), tc.args), tc.args)
|
|
}
|
|
}
|
|
|
|
func TestJSONFieldsAcceptSpaceSeparatedValue(t *testing.T) {
|
|
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.URL.Path {
|
|
case "/api/v1/user/orgs":
|
|
_, _ = w.Write([]byte(`[{"id":1,"username":"team","full_name":"Team"}]`))
|
|
case "/api/v1/repos/search":
|
|
require.Equal(t, "foo", r.URL.Query().Get("q"))
|
|
_, _ = w.Write([]byte(`{"data":[{"id":2,"full_name":"alice/foo"}]}`))
|
|
default:
|
|
t.Errorf("unexpected request %s", r.URL.Path)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
deps, out, _ := serverDeps(t, server.URL)
|
|
for _, args := range [][]string{{"org", "list", "--json", "id,name"}, {"org", "list", "--json=id,name"}, {"--json", "id,name", "org", "list"}} {
|
|
out.Reset()
|
|
require.NoError(t, Execute(context.Background(), deps, args), args)
|
|
require.JSONEq(t, `[{"id":1,"name":"team"}]`, out.String(), args)
|
|
}
|
|
out.Reset()
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"org", "list", "--json"}))
|
|
require.Contains(t, out.String(), `"full_name":"Team"`)
|
|
out.Reset()
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"search", "repo", "foo", "--json"}))
|
|
require.Contains(t, out.String(), `"full_name":"alice/foo"`)
|
|
out.Reset()
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"search", "repo", "--json", "foo"}))
|
|
require.Contains(t, out.String(), `"full_name":"alice/foo"`)
|
|
}
|
|
|
|
func TestSudoSendsHeaderAfterIdentityCheck(t *testing.T) {
|
|
seen := []string{}
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
seen = append(seen, r.URL.Path+" "+r.Header.Get("Sudo"))
|
|
if r.URL.Path == "/api/v1/user" {
|
|
_, _ = w.Write([]byte(`{"login":"alice"}`))
|
|
return
|
|
}
|
|
_, _ = w.Write([]byte(`{"id":7,"full_name":"alice/project"}`))
|
|
}))
|
|
defer server.Close()
|
|
deps, out, _ := serverDeps(t, server.URL)
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"--sudo", "bob", "repo", "view", "--repo", "alice/project", "--json=full_name"}))
|
|
require.Equal(t, []string{"/api/v1/user ", "/api/v1/repos/alice/project bob"}, seen)
|
|
require.JSONEq(t, `{"full_name":"alice/project"}`, out.String())
|
|
requireUsageBeforeRequests(t, "--sudo cannot be used with auth commands", "auth", "status", "--sudo", "bob")
|
|
requireUsageBeforeRequests(t, "--sudo requires a username", "repo", "view", "--sudo", "")
|
|
}
|
|
|
|
func TestJSONFieldErrorsListAvailableFields(t *testing.T) {
|
|
requireUsageBeforeRequests(t, "unknown JSON field \"titel\"\navailable fields: assignees, author, body", "issue", "list", "--json", "number,titel")
|
|
requireUsageBeforeRequests(t, "unknown JSON field \"workflow\"\navailable fields: actor, attempt, branch", "run", "list", "--json", "workflow")
|
|
requireUsageBeforeRequests(t, "--json= needs field names; use --json alone for every field", "pr", "list", "--json=")
|
|
requireUsageBeforeRequests(t, "version prints text; --jq is not supported", "version", "--jq", ".")
|
|
}
|
|
|
|
func TestJQImpliesJSONAndShorthands(t *testing.T) {
|
|
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
_, _ = w.Write([]byte(`[{"id":1,"username":"team","full_name":"Team"}]`))
|
|
}))
|
|
defer server.Close()
|
|
for _, args := range [][]string{{"org", "list", "--jq", ".[].name"}, {"org", "list", "-q", ".[].name"}, {"-q", ".[0].full_name", "org", "list"}} {
|
|
deps, out, _ := serverDeps(t, server.URL)
|
|
require.NoError(t, Execute(context.Background(), deps, args), args)
|
|
require.Contains(t, []string{"team\n", "Team\n"}, out.String(), args)
|
|
}
|
|
deps, _, _ := testDeps(t)
|
|
flags := NewRoot(deps).PersistentFlags()
|
|
require.Equal(t, "yes", flags.ShorthandLookup("y").Name)
|
|
require.Equal(t, "jq", flags.ShorthandLookup("q").Name)
|
|
}
|
|
|
|
func TestRawJSONCommandsKeepJQToThemselves(t *testing.T) {
|
|
deps, out, _ := testDeps(t)
|
|
root := NewRoot(deps)
|
|
root.AddCommand(&cobra.Command{Use: "raw", Annotations: map[string]string{"tea.text": "raw prints text", "tea.rawjson": "true"}, RunE: func(cmd *cobra.Command, _ []string) error {
|
|
_, err := fmt.Fprintf(cmd.OutOrStdout(), "json=%q", cmd.Flag("json").Value)
|
|
return err
|
|
}})
|
|
root.SetArgs([]string{"raw", "--jq", ".x"})
|
|
require.NoError(t, root.Execute())
|
|
require.Equal(t, `json=""`, out.String())
|
|
root.SetArgs([]string{"raw", "--json"})
|
|
require.ErrorContains(t, root.Execute(), "raw prints text; --json is not supported")
|
|
}
|
|
|
|
func TestRootHelpGroupsCommandsAndTopics(t *testing.T) {
|
|
deps, out, _ := testDeps(t)
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"alias", "set", "co", "pr checkout"}))
|
|
out.Reset()
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"--help"}))
|
|
help := out.String()
|
|
for _, want := range []string{
|
|
"Work seamlessly with Gitea from the command line.\n\nUSAGE\n tea <command> <subcommand> [flags]\n",
|
|
"\nCORE COMMANDS\n auth: ",
|
|
"\n repo: ",
|
|
"\nACTIONS COMMANDS\n run: ",
|
|
"\nADDITIONAL COMMANDS\n admin: ",
|
|
"\n completion: Generate shell completion scripts\n",
|
|
"\nALIAS COMMANDS\n co: Alias for \"pr checkout\"\n",
|
|
"\nHELP TOPICS\n environment: ",
|
|
"\nFLAGS\n --help Show help for command\n --version Show tea version\n",
|
|
"\nEXAMPLES\n $ tea issue create\n",
|
|
"\nLEARN MORE\n Use `tea <command> <subcommand> --help` for more information about a command.\n",
|
|
} {
|
|
require.Contains(t, help, want)
|
|
}
|
|
require.NotContains(t, help, " help:")
|
|
require.NotContains(t, help, " man:")
|
|
// Every visible top-level command belongs to one root help group.
|
|
for _, cmd := range NewRoot(deps).Commands() {
|
|
if cmd.IsAvailableCommand() {
|
|
require.Contains(t, []string{"core", "actions", "additional"}, cmd.GroupID, cmd.Name())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCommandHelpShowsRelevantFlagsOnly(t *testing.T) {
|
|
deps, out, _ := testDeps(t)
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"issue", "list", "--help"}))
|
|
help := out.String()
|
|
for _, want := range []string{
|
|
"\nUSAGE\n tea issue list [flags]\n",
|
|
"\nALIASES\n tea issue ls\n",
|
|
"\nFLAGS\n --assignee string ",
|
|
"\nINHERITED FLAGS\n -R, --repo OWNER/REPO ",
|
|
"\n --json fields ",
|
|
"\n -q, --jq expression ",
|
|
"\n -h, --help Show help for command\n\n " + globalFlagsLine + "\n",
|
|
"\nJSON FIELDS\n assignees, author, body, ",
|
|
} {
|
|
require.Contains(t, help, want)
|
|
}
|
|
for _, unwanted := range []string{"--sudo string", "--yes", `[="*"]`, "Global Flags:"} {
|
|
require.NotContains(t, help, unwanted)
|
|
}
|
|
out.Reset()
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"label", "list", "--help"}))
|
|
require.Contains(t, out.String(), "\nFLAGS\n --limit int Maximum results (1..100) (default 30)\n --org string ")
|
|
for _, args := range [][]string{{"version", "--help"}, {"alias", "list", "--help"}, {"auth", "status", "--help"}} {
|
|
out.Reset()
|
|
require.NoError(t, Execute(context.Background(), deps, args))
|
|
require.NotContains(t, out.String(), "--repo", args)
|
|
require.Contains(t, out.String(), globalFlagsLine, args)
|
|
}
|
|
out.Reset()
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"version", "--help"}))
|
|
require.NotContains(t, out.String(), "--json fields")
|
|
out.Reset()
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"issue", "--help"}))
|
|
require.Contains(t, out.String(), "\nUSAGE\n tea issue <command> [flags]\n\nAVAILABLE COMMANDS\n attachment: ")
|
|
require.NotContains(t, out.String(), "JSON FIELDS")
|
|
}
|
|
|
|
func TestHelpConfirmAndArgumentsAnnotations(t *testing.T) {
|
|
deps, out, _ := testDeps(t)
|
|
root := NewRoot(deps)
|
|
issue, _, err := root.Find([]string{"issue"})
|
|
require.NoError(t, err)
|
|
issue.AddCommand(&cobra.Command{Use: "drop <number>", Short: "Drop an issue", Example: "# Drop issue 7\n$ tea issue drop 7", Annotations: map[string]string{"tea.confirm": "true", "tea.arguments": "An issue is a number or a URL."}, RunE: func(*cobra.Command, []string) error { return nil }})
|
|
root.SetArgs([]string{"issue", "drop", "--help"})
|
|
require.NoError(t, root.Execute())
|
|
require.Contains(t, out.String(), "\n -y, --yes ")
|
|
require.Contains(t, out.String(), "\nARGUMENTS\n An issue is a number or a URL.\n\nJSON FIELDS\n")
|
|
require.Contains(t, out.String(), "\nEXAMPLES\n # Drop issue 7\n $ tea issue drop 7\n")
|
|
}
|
|
|
|
func TestHelpTopicsAndUnknownTopics(t *testing.T) {
|
|
deps, out, _ := testDeps(t)
|
|
for args, want := range map[string]string{
|
|
"help environment": " TEA_CONFIG_DIR ",
|
|
"help exit-codes": " 4 authentication",
|
|
"help formatting": "--jq runs inside tea",
|
|
"help issue list": "\nUSAGE\n tea issue list [flags]\n",
|
|
"environment": " -R, --repo OWNER/REPO ",
|
|
} {
|
|
out.Reset()
|
|
require.NoError(t, Execute(context.Background(), deps, strings.Fields(args)), args)
|
|
require.Contains(t, out.String(), want, args)
|
|
}
|
|
for args, want := range map[string]string{
|
|
"help nope": `unknown help topic "nope"; run tea help to list commands and topics`,
|
|
"help isue": `unknown help topic "isue"; did you mean issue?`,
|
|
"help isue list": `unknown help topic "isue list"; did you mean issue?`,
|
|
"help env": `unknown help topic "env"; did you mean environment?`,
|
|
"help issue nope": `unknown help topic "issue nope"; run tea help to list commands and topics`,
|
|
} {
|
|
out.Reset()
|
|
err := Execute(context.Background(), deps, strings.Fields(args))
|
|
require.Equal(t, clierr.Usage, clierr.ExitCode(err), args)
|
|
require.EqualError(t, err, want, args)
|
|
require.Empty(t, out.String(), args)
|
|
}
|
|
values, _ := complete(t, deps, out, "help", "e")
|
|
require.Equal(t, []string{"environment\tEnvironment variables and global flags", "exit-codes\tExit codes used by tea"}, values)
|
|
values, _ = complete(t, deps, out, "help", "issue", "l")
|
|
require.Equal(t, []string{"list\tList repository issues"}, values)
|
|
}
|
|
|
|
func TestVerbAndFlagAliases(t *testing.T) {
|
|
deps, _, _ := testDeps(t)
|
|
root := NewRoot(deps)
|
|
for _, args := range [][]string{{"issue", "ls"}, {"label", "rm"}, {"pr", "review", "ls"}} {
|
|
cmd, _, err := root.Find(args)
|
|
require.NoError(t, err)
|
|
require.Equal(t, map[string]string{"ls": "list", "rm": "delete"}[args[len(args)-1]], cmd.Name(), args)
|
|
}
|
|
// Aliases never shadow a sibling command.
|
|
walkCommands(root, func(cmd *cobra.Command) {
|
|
names := map[string]string{}
|
|
for _, child := range cmd.Commands() {
|
|
for _, name := range append([]string{child.Name()}, child.Aliases...) {
|
|
require.Empty(t, names[name], "%s %s is both %s and %s", cmd.CommandPath(), name, names[name], child.Name())
|
|
names[name] = child.Name()
|
|
}
|
|
}
|
|
})
|
|
var labels []string
|
|
edit := &cobra.Command{Use: "edit", RunE: func(*cobra.Command, []string) error { return nil }}
|
|
edit.Flags().StringSliceVar(&labels, "add-label", nil, "Add labels")
|
|
flagAliases(edit, map[string]string{"add-labels": "add-label"})
|
|
edit.SetArgs([]string{"--add-labels", "bug", "--add-label", "docs"})
|
|
require.NoError(t, edit.Execute())
|
|
require.Equal(t, []string{"bug", "docs"}, labels)
|
|
require.NotContains(t, edit.UsageString(), "add-labels")
|
|
}
|
|
|
|
// TestHelpTextsAreComplete checks the help texts of every command: a Short line without a final period,
|
|
// a capitalized hyphenated verb ("Mark-read …") or a sibling's wording, an Example on every command and a
|
|
// Long text on every command group. Area streams remove the commands they complete from the allowlists
|
|
// below; S8 deletes the allowlists.
|
|
func TestHelpTextsAreComplete(t *testing.T) {
|
|
deps, _, _ := testDeps(t)
|
|
pending := func(list string) map[string]bool {
|
|
set := map[string]bool{}
|
|
for path := range strings.Lines(list) {
|
|
if path = strings.TrimSpace(path); path != "" {
|
|
set[path] = true
|
|
}
|
|
}
|
|
return set
|
|
}
|
|
shorts, examples, longs := pending(shortsToFix), pending(examplesToWrite), pending(longsToWrite)
|
|
walkCommands(NewRoot(deps), func(cmd *cobra.Command) {
|
|
if !cmd.HasParent() || !cmd.IsAvailableCommand() {
|
|
return
|
|
}
|
|
path := strings.TrimPrefix(cmd.CommandPath(), "tea ")
|
|
generated := strings.Contains(cmd.Name(), "-") && strings.HasPrefix(cmd.Short, titleCase(cmd.Name())+" ")
|
|
siblings := slices.ContainsFunc(cmd.Parent().Commands(), func(other *cobra.Command) bool { return other != cmd && other.Short == cmd.Short })
|
|
badShort := cmd.Short == "" || strings.HasSuffix(cmd.Short, ".") || unicode.IsLower(rune(cmd.Short[0])) || generated || siblings
|
|
checks := []struct {
|
|
name string
|
|
missing bool
|
|
pending map[string]bool
|
|
}{
|
|
{"a clear Short", badShort, shorts},
|
|
{"an Example", cmd.Example == "" && cmd.Annotations["tea.group"] == "", examples},
|
|
{"a Long text", cmd.Long == "" && cmd.Annotations["tea.group"] != "", longs},
|
|
}
|
|
for _, check := range checks {
|
|
switch {
|
|
case check.missing && !check.pending[path]:
|
|
t.Errorf("%s needs %s", path, check.name)
|
|
case !check.missing && check.pending[path]:
|
|
t.Errorf("%s has %s now; remove it from the allowlist", path, check.name)
|
|
}
|
|
delete(check.pending, path)
|
|
}
|
|
})
|
|
for _, check := range []map[string]bool{shorts, examples, longs} {
|
|
for path := range check {
|
|
t.Errorf("%s is allowlisted but is not a command", path)
|
|
}
|
|
}
|
|
}
|
|
|
|
// walkCommands calls visit for cmd and each of its descendants.
|
|
func walkCommands(cmd *cobra.Command, visit func(*cobra.Command)) {
|
|
visit(cmd)
|
|
for _, child := range cmd.Commands() {
|
|
walkCommands(child, visit)
|
|
}
|
|
}
|
|
|
|
// shortsToFix, examplesToWrite and longsToWrite list the commands whose help texts area streams
|
|
// have yet to complete; see TestHelpTextsAreComplete.
|
|
const (
|
|
shortsToFix = `
|
|
config get
|
|
config list
|
|
notification mark-read
|
|
notification mark-unread
|
|
pr review cancel-request
|
|
`
|
|
examplesToWrite = `
|
|
admin user create
|
|
admin user delete
|
|
admin user edit
|
|
admin user list
|
|
admin user view
|
|
alias delete
|
|
alias list
|
|
alias set
|
|
api
|
|
auth login
|
|
auth logout
|
|
auth refresh
|
|
auth setup-git
|
|
auth status
|
|
auth switch
|
|
branch create
|
|
branch delete
|
|
branch list
|
|
branch protect
|
|
branch rename
|
|
branch unprotect
|
|
branch view
|
|
config get
|
|
config list
|
|
config path
|
|
config set
|
|
doctor
|
|
issue attachment delete
|
|
issue attachment download
|
|
issue attachment edit
|
|
issue attachment list
|
|
issue attachment upload
|
|
issue attachment view
|
|
issue close
|
|
issue comment create
|
|
issue comment delete
|
|
issue comment edit
|
|
issue comment list
|
|
issue comment view
|
|
issue create
|
|
issue edit
|
|
issue list
|
|
issue reopen
|
|
issue view
|
|
label create
|
|
label delete
|
|
label edit
|
|
label export
|
|
label list
|
|
milestone close
|
|
milestone create
|
|
milestone delete
|
|
milestone edit
|
|
milestone issue add
|
|
milestone issue list
|
|
milestone issue remove
|
|
milestone list
|
|
milestone reopen
|
|
milestone view
|
|
notification list
|
|
notification mark-read
|
|
notification mark-unread
|
|
notification pin
|
|
notification unpin
|
|
notification view
|
|
notification watch
|
|
org create
|
|
org delete
|
|
org edit
|
|
org list
|
|
org member list
|
|
org member private
|
|
org member public
|
|
org member remove
|
|
org view
|
|
pr attachment delete
|
|
pr attachment download
|
|
pr attachment edit
|
|
pr attachment list
|
|
pr attachment upload
|
|
pr attachment view
|
|
pr checkout
|
|
pr checks
|
|
pr clean
|
|
pr close
|
|
pr comment create
|
|
pr comment delete
|
|
pr comment edit
|
|
pr comment list
|
|
pr comment view
|
|
pr create
|
|
pr diff
|
|
pr edit
|
|
pr list
|
|
pr merge
|
|
pr reopen
|
|
pr review
|
|
pr review cancel-request
|
|
pr review comments
|
|
pr review delete
|
|
pr review dismiss
|
|
pr review list
|
|
pr review reply
|
|
pr review request
|
|
pr review resolve
|
|
pr review submit
|
|
pr review undismiss
|
|
pr review unresolve
|
|
pr review view
|
|
pr status
|
|
pr view
|
|
release asset delete
|
|
release asset download
|
|
release asset edit
|
|
release asset list
|
|
release asset upload
|
|
release create
|
|
release delete
|
|
release edit
|
|
release list
|
|
release publish
|
|
release view
|
|
repo archive
|
|
repo clone
|
|
repo create
|
|
repo delete
|
|
repo edit
|
|
repo fork
|
|
repo list
|
|
repo migrate
|
|
repo set-default
|
|
repo sync
|
|
repo view
|
|
run artifact download
|
|
run artifact list
|
|
run cancel
|
|
run delete
|
|
run jobs
|
|
run list
|
|
run logs
|
|
run rerun
|
|
run view
|
|
run watch
|
|
search issue
|
|
search pr
|
|
search repo
|
|
secret delete
|
|
secret list
|
|
secret set
|
|
ssh-key create
|
|
ssh-key delete
|
|
ssh-key list
|
|
ssh-key view
|
|
status
|
|
team create
|
|
team delete
|
|
team edit
|
|
team list
|
|
team member add
|
|
team member list
|
|
team member remove
|
|
team repo add
|
|
team repo list
|
|
team repo remove
|
|
team view
|
|
time create
|
|
time delete
|
|
time list
|
|
time reset
|
|
variable delete
|
|
variable list
|
|
variable set
|
|
variable view
|
|
webhook create
|
|
webhook delete
|
|
webhook edit
|
|
webhook list
|
|
webhook view
|
|
wiki create
|
|
wiki delete
|
|
wiki edit
|
|
wiki list
|
|
wiki revisions
|
|
wiki view
|
|
workflow disable
|
|
workflow enable
|
|
workflow list
|
|
workflow run
|
|
workflow view
|
|
`
|
|
longsToWrite = `
|
|
admin
|
|
admin user
|
|
alias
|
|
auth
|
|
branch
|
|
issue
|
|
issue attachment
|
|
issue comment
|
|
label
|
|
milestone
|
|
milestone issue
|
|
notification
|
|
org
|
|
org member
|
|
pr
|
|
pr attachment
|
|
pr comment
|
|
release
|
|
release asset
|
|
repo
|
|
run
|
|
run artifact
|
|
search
|
|
secret
|
|
ssh-key
|
|
team
|
|
team member
|
|
team repo
|
|
time
|
|
variable
|
|
webhook
|
|
wiki
|
|
workflow
|
|
`
|
|
)
|