Files
larksuite-cli/tests/cli_e2e/core_test.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
}