mirror of
https://github.com/larksuite/cli.git
synced 2026-07-03 14:02:43 +08:00
192 lines
6.5 KiB
Go
192 lines
6.5 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/larksuite/cli/internal/build"
|
|
"github.com/larksuite/cli/internal/cmdutil"
|
|
"github.com/larksuite/cli/internal/core"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func writeUpdateState(t *testing.T, dir, latest string) {
|
|
t.Helper()
|
|
data := fmt.Sprintf(`{"latest_version":%q,"checked_at":%d}`, latest, time.Now().Unix())
|
|
if err := os.WriteFile(filepath.Join(dir, "update-state.json"), []byte(data), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestReadYes(t *testing.T) {
|
|
cases := map[string]bool{
|
|
"y\n": true, "Y\n": true, "yes\n": true, "YES\n": true, " y \n": true,
|
|
"n\n": false, "\n": false, "": false, "nope\n": false, "yeah\n": false,
|
|
}
|
|
for in, want := range cases {
|
|
if got := readYes(strings.NewReader(in)); got != want {
|
|
t.Errorf("readYes(%q) = %v, want %v", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIsBareRootInvocation(t *testing.T) {
|
|
orig := rawInvocationArgs
|
|
t.Cleanup(func() { rawInvocationArgs = orig })
|
|
|
|
rawInvocationArgs = nil
|
|
if !isBareRootInvocation([]string{}) {
|
|
t.Error("empty args + no raw flag tokens should be bare")
|
|
}
|
|
rawInvocationArgs = []string{"--profile", "x"}
|
|
if isBareRootInvocation([]string{}) {
|
|
t.Error("flag token present → not bare")
|
|
}
|
|
rawInvocationArgs = nil
|
|
if isBareRootInvocation([]string{"im"}) {
|
|
t.Error("positional arg → not bare")
|
|
}
|
|
}
|
|
|
|
func TestOfferRootUpgrade(t *testing.T) {
|
|
origV := build.Version
|
|
build.Version = "1.0.0" // release version so shouldSkip()==false
|
|
t.Cleanup(func() { build.Version = origV })
|
|
|
|
origRun := runRootUpgrade
|
|
t.Cleanup(func() { runRootUpgrade = origRun })
|
|
|
|
// This test builds a Factory literal (no NewDefault), so it never runs
|
|
// workspace detection; pin the process-global workspace to Local so
|
|
// statePath() resolves under LARKSUITE_CLI_CONFIG_DIR rather than a stale
|
|
// subdir inherited from a prior test in the package.
|
|
origWS := core.CurrentWorkspace()
|
|
t.Cleanup(func() { core.SetCurrentWorkspace(origWS) })
|
|
core.SetCurrentWorkspace(core.WorkspaceLocal)
|
|
|
|
cases := []struct {
|
|
name string
|
|
in, out, err bool
|
|
input string
|
|
latest string // "" → no state file (CheckCached nil)
|
|
optOut bool
|
|
wantPrompt, wantRun bool
|
|
}{
|
|
{"all-tty+y", true, true, true, "y\n", "2.0.0", false, true, true},
|
|
{"all-tty+yes", true, true, true, "yes\n", "2.0.0", false, true, true},
|
|
{"all-tty+n", true, true, true, "n\n", "2.0.0", false, true, false},
|
|
{"all-tty+empty", true, true, true, "\n", "2.0.0", false, true, false},
|
|
{"all-tty+eof", true, true, true, "", "2.0.0", false, true, false},
|
|
{"stdin-not-tty", false, true, true, "y\n", "2.0.0", false, false, false},
|
|
{"stdout-not-tty", true, false, true, "y\n", "2.0.0", false, false, false},
|
|
{"stderr-not-tty", true, true, false, "y\n", "2.0.0", false, false, false},
|
|
{"no-newer-version", true, true, true, "y\n", "", false, false, false},
|
|
{"already-latest", true, true, true, "y\n", "1.0.0", false, false, false}, // post-upgrade: current == cached latest → no prompt
|
|
{"cache-older-than-current", true, true, true, "y\n", "0.9.0", false, false, false},
|
|
{"opt-out", true, true, true, "y\n", "2.0.0", true, false, false},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", dir)
|
|
// Clear env that update.shouldSkip treats as "suppress" so the
|
|
// test is deterministic regardless of host (GitHub Actions sets
|
|
// CI=true, which would otherwise suppress the prompt).
|
|
t.Setenv("CI", "")
|
|
t.Setenv("BUILD_NUMBER", "")
|
|
t.Setenv("RUN_ID", "")
|
|
t.Setenv("LARKSUITE_CLI_NO_UPDATE_NOTIFIER", "")
|
|
if tc.latest != "" {
|
|
writeUpdateState(t, dir, tc.latest)
|
|
}
|
|
if tc.optOut {
|
|
t.Setenv("LARKSUITE_CLI_NO_UPDATE_NOTIFIER", "1")
|
|
}
|
|
called := false
|
|
runRootUpgrade = func(*cobra.Command) { called = true }
|
|
|
|
var errBuf bytes.Buffer
|
|
f := &cmdutil.Factory{IOStreams: &cmdutil.IOStreams{
|
|
In: strings.NewReader(tc.input),
|
|
Out: &bytes.Buffer{},
|
|
ErrOut: &errBuf,
|
|
IsTerminal: tc.in,
|
|
OutIsTerminal: tc.out,
|
|
StderrIsTerminal: tc.err,
|
|
}}
|
|
offerRootUpgrade(f, &cobra.Command{})
|
|
|
|
gotPrompt := strings.Contains(errBuf.String(), "available")
|
|
if gotPrompt != tc.wantPrompt {
|
|
t.Errorf("prompt: got %v want %v (stderr=%q)", gotPrompt, tc.wantPrompt, errBuf.String())
|
|
}
|
|
if called != tc.wantRun {
|
|
t.Errorf("runRootUpgrade called: got %v want %v", called, tc.wantRun)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestInstallRootUpgradePromptPreservesInner(t *testing.T) {
|
|
orig := rawInvocationArgs
|
|
t.Cleanup(func() { rawInvocationArgs = orig })
|
|
rawInvocationArgs = nil
|
|
|
|
innerCalls := 0
|
|
root := &cobra.Command{Use: "lark-cli"}
|
|
root.RunE = func(cmd *cobra.Command, args []string) error { innerCalls++; return nil }
|
|
|
|
f := &cmdutil.Factory{IOStreams: &cmdutil.IOStreams{
|
|
In: strings.NewReader(""), Out: &bytes.Buffer{}, ErrOut: &bytes.Buffer{},
|
|
}}
|
|
installRootUpgradePrompt(f, root)
|
|
|
|
if err := root.RunE(root, []string{}); err != nil {
|
|
t.Fatalf("bare RunE err = %v", err)
|
|
}
|
|
if err := root.RunE(root, []string{"im"}); err != nil {
|
|
t.Fatalf("non-bare RunE err = %v", err)
|
|
}
|
|
if innerCalls != 2 {
|
|
t.Errorf("inner RunE should run for both bare and non-bare, got %d", innerCalls)
|
|
}
|
|
}
|
|
|
|
// TestRunRootUpgradeDispatchesToUpdate covers the real runRootUpgrade dispatch
|
|
// path (not the stub used elsewhere): from any command it must locate the
|
|
// registered "update" subcommand via cmd.Root() and invoke its RunE.
|
|
func TestRunRootUpgradeDispatchesToUpdate(t *testing.T) {
|
|
root := &cobra.Command{Use: "lark-cli"}
|
|
ran := 0
|
|
root.AddCommand(&cobra.Command{Use: "update", RunE: func(*cobra.Command, []string) error { ran++; return nil }})
|
|
child := &cobra.Command{Use: "im"}
|
|
root.AddCommand(child)
|
|
|
|
runRootUpgrade(child) // child.Root() resolves to root, which has "update"
|
|
|
|
if ran != 1 {
|
|
t.Errorf("runRootUpgrade should locate and run update's RunE once, got %d", ran)
|
|
}
|
|
}
|
|
|
|
// TestInstallRootUpgradePromptNilInnerNoop covers the inner == nil guard:
|
|
// when root has no RunE, installRootUpgradePrompt must not wrap it.
|
|
func TestInstallRootUpgradePromptNilInnerNoop(t *testing.T) {
|
|
root := &cobra.Command{Use: "lark-cli"} // RunE is nil
|
|
f := &cmdutil.Factory{IOStreams: &cmdutil.IOStreams{
|
|
In: strings.NewReader(""), Out: &bytes.Buffer{}, ErrOut: &bytes.Buffer{},
|
|
}}
|
|
installRootUpgradePrompt(f, root)
|
|
if root.RunE != nil {
|
|
t.Error("installRootUpgradePrompt must not wrap a nil RunE (inner==nil guard)")
|
|
}
|
|
}
|