mirror of
https://github.com/larksuite/cli.git
synced 2026-07-03 14:02:43 +08:00
310 lines
8.0 KiB
Go
310 lines
8.0 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package clie2e
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestResolveBinaryPath(t *testing.T) {
|
|
t.Run("request binary path wins", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
reqBin := mustWriteExecutable(t, filepath.Join(tmpDir, "req-bin"))
|
|
envBin := mustWriteExecutable(t, filepath.Join(tmpDir, "env-bin"))
|
|
t.Setenv(EnvBinaryPath, envBin)
|
|
|
|
got, err := ResolveBinaryPath(Request{BinaryPath: reqBin})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, reqBin, got)
|
|
})
|
|
|
|
t.Run("uses env binary path", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
envBin := mustWriteExecutable(t, filepath.Join(tmpDir, "env-bin"))
|
|
t.Setenv(EnvBinaryPath, envBin)
|
|
|
|
got, err := ResolveBinaryPath(Request{})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, envBin, got)
|
|
})
|
|
|
|
t.Run("uses project root binary", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testsDir := filepath.Join(tmpDir, projectRootMarkerDir)
|
|
require.NoError(t, os.MkdirAll(testsDir, 0o755))
|
|
projectBin := mustWriteExecutable(t, filepath.Join(tmpDir, cliBinaryName))
|
|
|
|
oldWD, err := os.Getwd()
|
|
require.NoError(t, err)
|
|
require.NoError(t, os.Chdir(testsDir))
|
|
defer func() {
|
|
require.NoError(t, os.Chdir(oldWD))
|
|
}()
|
|
|
|
t.Setenv(EnvBinaryPath, "")
|
|
got, err := ResolveBinaryPath(Request{})
|
|
require.NoError(t, err)
|
|
assertSamePath(t, projectBin, got)
|
|
})
|
|
|
|
t.Run("rejects non-executable path", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
file := filepath.Join(tmpDir, "not-exec")
|
|
require.NoError(t, os.WriteFile(file, []byte("plain"), 0o644))
|
|
|
|
_, err := ResolveBinaryPath(Request{BinaryPath: file})
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "not executable")
|
|
})
|
|
}
|
|
|
|
func TestBuildArgs(t *testing.T) {
|
|
t.Run("encodes json payloads", func(t *testing.T) {
|
|
args, err := BuildArgs(Request{
|
|
Args: []string{"task", "+create"},
|
|
Params: map[string]any{"task_guid": "abc"},
|
|
Data: map[string]any{"summary": "hello"},
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []string{
|
|
"task", "+create",
|
|
"--params", `{"task_guid":"abc"}`,
|
|
"--data", `{"summary":"hello"}`,
|
|
}, args)
|
|
})
|
|
|
|
t.Run("adds default-as and format when set", func(t *testing.T) {
|
|
args, err := BuildArgs(Request{
|
|
Args: []string{"task", "+update"},
|
|
DefaultAs: "user",
|
|
Format: "pretty",
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []string{"task", "+update", "--as", "user", "--format", "pretty"}, args)
|
|
})
|
|
|
|
t.Run("requires args", func(t *testing.T) {
|
|
_, err := BuildArgs(Request{})
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "args are required")
|
|
})
|
|
}
|
|
|
|
func TestSkipWithoutUserToken(t *testing.T) {
|
|
t.Run("returns immediately when env user access token exists", func(t *testing.T) {
|
|
t.Setenv("LARKSUITE_CLI_USER_ACCESS_TOKEN", "uat-from-env")
|
|
|
|
ran := false
|
|
ok := t.Run("inner", func(t *testing.T) {
|
|
SkipWithoutUserToken(t)
|
|
ran = true
|
|
})
|
|
require.True(t, ok)
|
|
assert.True(t, ran)
|
|
})
|
|
|
|
t.Run("accepts verified local auth status", func(t *testing.T) {
|
|
fake := newFakeCLI(t)
|
|
t.Setenv("LARKSUITE_CLI_USER_ACCESS_TOKEN", "")
|
|
t.Setenv(EnvBinaryPath, fake.BinaryPath)
|
|
t.Setenv("FAKE_AUTH_STATUS_STDOUT", `{"identity":"user","verified":true}`)
|
|
t.Setenv("FAKE_AUTH_STATUS_EXIT_CODE", "0")
|
|
|
|
ran := false
|
|
ok := t.Run("inner", func(t *testing.T) {
|
|
SkipWithoutUserToken(t)
|
|
ran = true
|
|
})
|
|
require.True(t, ok)
|
|
assert.True(t, ran)
|
|
})
|
|
|
|
t.Run("skips when local auth is not user", func(t *testing.T) {
|
|
fake := newFakeCLI(t)
|
|
t.Setenv("LARKSUITE_CLI_USER_ACCESS_TOKEN", "")
|
|
t.Setenv(EnvBinaryPath, fake.BinaryPath)
|
|
t.Setenv("FAKE_AUTH_STATUS_STDOUT", `{"identity":"bot","verified":false}`)
|
|
t.Setenv("FAKE_AUTH_STATUS_EXIT_CODE", "0")
|
|
|
|
ran := false
|
|
ok := t.Run("inner", func(t *testing.T) {
|
|
SkipWithoutUserToken(t)
|
|
ran = true
|
|
})
|
|
require.True(t, ok)
|
|
assert.False(t, ran)
|
|
})
|
|
}
|
|
|
|
func TestRunCmd(t *testing.T) {
|
|
t.Run("returns stdout json on success", func(t *testing.T) {
|
|
fake := newFakeCLI(t)
|
|
result, err := RunCmd(context.Background(), Request{
|
|
BinaryPath: fake.BinaryPath,
|
|
Args: []string{"--stdout-json", `{"ok":true}`},
|
|
})
|
|
require.NoError(t, err)
|
|
result.AssertExitCode(t, 0)
|
|
result.AssertStdoutStatus(t, true)
|
|
|
|
outMap, ok := result.StdoutJSON(t).(map[string]any)
|
|
require.True(t, ok)
|
|
assert.Equal(t, true, outMap["ok"])
|
|
})
|
|
|
|
t.Run("captures stderr and exit code on failure", func(t *testing.T) {
|
|
fake := newFakeCLI(t)
|
|
result, err := RunCmd(context.Background(), Request{
|
|
BinaryPath: fake.BinaryPath,
|
|
Args: []string{"--stderr-json", `{"ok":false}`, "--exit", "3"},
|
|
})
|
|
require.NoError(t, err)
|
|
result.AssertExitCode(t, 3)
|
|
assert.Error(t, result.RunErr)
|
|
|
|
errMap, ok := result.StderrJSON(t).(map[string]any)
|
|
require.True(t, ok)
|
|
assert.Equal(t, false, errMap["ok"])
|
|
})
|
|
|
|
t.Run("passes explicit default-as as flag", func(t *testing.T) {
|
|
fake := newFakeCLI(t)
|
|
result, err := RunCmd(context.Background(), Request{
|
|
BinaryPath: fake.BinaryPath,
|
|
Args: []string{"emit-arg", "--as"},
|
|
DefaultAs: "user",
|
|
})
|
|
require.NoError(t, err)
|
|
result.AssertExitCode(t, 0)
|
|
assert.Equal(t, "user", strings.TrimSpace(result.Stdout))
|
|
})
|
|
|
|
t.Run("asserts stdout code payloads", func(t *testing.T) {
|
|
fake := newFakeCLI(t)
|
|
result, err := RunCmd(context.Background(), Request{
|
|
BinaryPath: fake.BinaryPath,
|
|
Args: []string{"--stdout-json", `{"code":0,"data":{"id":"x"}}`},
|
|
Format: "json",
|
|
})
|
|
require.NoError(t, err)
|
|
result.AssertExitCode(t, 0)
|
|
result.AssertStdoutStatus(t, 0)
|
|
})
|
|
|
|
t.Run("passes stdin to process", func(t *testing.T) {
|
|
fake := newFakeCLI(t)
|
|
result, err := RunCmd(context.Background(), Request{
|
|
BinaryPath: fake.BinaryPath,
|
|
Args: []string{"emit-stdin"},
|
|
Stdin: []byte("hello from stdin\n"),
|
|
})
|
|
require.NoError(t, err)
|
|
result.AssertExitCode(t, 0)
|
|
assert.Equal(t, "hello from stdin\n", result.Stdout)
|
|
})
|
|
|
|
t.Run("injects user token env only for user commands", func(t *testing.T) {
|
|
t.Setenv("TEST_BOT1_APP_ID", "cli_app_test")
|
|
t.Setenv("TEST_USER_ACCESS_TOKEN", "uat_test")
|
|
|
|
env := buildCommandEnv(Request{DefaultAs: "user"})
|
|
assert.Contains(t, env, "LARKSUITE_CLI_APP_ID=cli_app_test")
|
|
assert.Contains(t, env, "LARKSUITE_CLI_USER_ACCESS_TOKEN=uat_test")
|
|
|
|
env = buildCommandEnv(Request{DefaultAs: "bot"})
|
|
assert.NotContains(t, env, "LARKSUITE_CLI_APP_ID=cli_app_test")
|
|
assert.NotContains(t, env, "LARKSUITE_CLI_USER_ACCESS_TOKEN=uat_test")
|
|
})
|
|
}
|
|
|
|
type fakeCLI struct {
|
|
BinaryPath string
|
|
}
|
|
|
|
func newFakeCLI(t *testing.T) fakeCLI {
|
|
t.Helper()
|
|
|
|
tmpDir := t.TempDir()
|
|
|
|
script := `#!/bin/sh
|
|
if [ "$1" = "auth" ] && [ "$2" = "status" ] && [ "$3" = "--verify" ]; then
|
|
if [ -n "$FAKE_AUTH_STATUS_STDOUT" ]; then
|
|
echo "$FAKE_AUTH_STATUS_STDOUT"
|
|
fi
|
|
exit "${FAKE_AUTH_STATUS_EXIT_CODE:-0}"
|
|
fi
|
|
|
|
if [ "$1" = "emit-arg" ]; then
|
|
key="$2"
|
|
shift 2
|
|
while [ "$#" -gt 1 ]; do
|
|
if [ "$1" = "$key" ]; then
|
|
echo "$2"
|
|
exit 0
|
|
fi
|
|
shift
|
|
done
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$1" = "emit-stdin" ]; then
|
|
cat
|
|
exit 0
|
|
fi
|
|
|
|
exit_code=0
|
|
while [ "$#" -gt 0 ]; do
|
|
case "$1" in
|
|
--stdout-json)
|
|
echo "$2"
|
|
shift 2
|
|
;;
|
|
--stderr-json)
|
|
echo "$2" >&2
|
|
shift 2
|
|
;;
|
|
--exit)
|
|
exit_code="$2"
|
|
shift 2
|
|
;;
|
|
*)
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
exit "$exit_code"
|
|
`
|
|
|
|
binaryPath := filepath.Join(tmpDir, "fake-"+cliBinaryName)
|
|
require.NoError(t, os.WriteFile(binaryPath, []byte(script), 0o755))
|
|
|
|
return fakeCLI{
|
|
BinaryPath: binaryPath,
|
|
}
|
|
}
|
|
|
|
func assertSamePath(t *testing.T, want string, got string) {
|
|
t.Helper()
|
|
gotReal, err := filepath.EvalSymlinks(got)
|
|
require.NoError(t, err)
|
|
wantReal, err := filepath.EvalSymlinks(want)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, wantReal, gotReal)
|
|
}
|
|
|
|
func mustWriteExecutable(t *testing.T, path string) string {
|
|
t.Helper()
|
|
require.NoError(t, os.WriteFile(path, []byte("#!/bin/sh\nexit 0\n"), 0o755))
|
|
absPath, err := filepath.Abs(path)
|
|
require.NoError(t, err)
|
|
return absPath
|
|
}
|