Files
larksuite-cli/shortcuts/common/validate_test.go
max c7ced37959 feat: unify minute artifacts output to ./minutes/{minute_token}/ (#604)
* feat: unify minute artifacts output to ./minutes/{minute_token}/

* fix: tighten path validation and batch-mode --output rejection

* style: translate comments to english and trim historical context

* style: translate leftover chinese comments in vc_notes

* refactor: address review findings across validate ordering, error types, JSON, tests

* fix: sanitize server-provided filename to prevent escape from artifact dir

* style: tighten flag help text for minutes/vc output flags

* docs: update minutes/vc skill docs for unified artifact layout
2026-04-23 16:37:33 +08:00

249 lines
6.4 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package common
import (
"os"
"path/filepath"
"testing"
"github.com/larksuite/cli/internal/vfs/localfileio"
"github.com/spf13/cobra"
)
// newTestRuntime creates a RuntimeContext with string flags for testing.
func newTestRuntime(flags map[string]string) *RuntimeContext {
cmd := &cobra.Command{Use: "test"}
for name := range flags {
cmd.Flags().String(name, "", "")
}
// Parse empty args so flags have defaults, then set values.
cmd.ParseFlags(nil)
for name, val := range flags {
cmd.Flags().Set(name, val)
}
return &RuntimeContext{Cmd: cmd}
}
func TestMutuallyExclusive(t *testing.T) {
tests := []struct {
name string
flags map[string]string
check []string
wantErr bool
}{
{
name: "none set",
flags: map[string]string{"a": "", "b": ""},
check: []string{"a", "b"},
wantErr: false,
},
{
name: "one set",
flags: map[string]string{"a": "x", "b": ""},
check: []string{"a", "b"},
wantErr: false,
},
{
name: "both set",
flags: map[string]string{"a": "x", "b": "y"},
check: []string{"a", "b"},
wantErr: true,
},
{
name: "three flags two set",
flags: map[string]string{"a": "x", "b": "", "c": "z"},
check: []string{"a", "b", "c"},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rt := newTestRuntime(tt.flags)
err := MutuallyExclusive(rt, tt.check...)
if (err != nil) != tt.wantErr {
t.Errorf("MutuallyExclusive() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestAtLeastOne(t *testing.T) {
tests := []struct {
name string
flags map[string]string
check []string
wantErr bool
}{
{
name: "none set",
flags: map[string]string{"a": "", "b": ""},
check: []string{"a", "b"},
wantErr: true,
},
{
name: "one set",
flags: map[string]string{"a": "x", "b": ""},
check: []string{"a", "b"},
wantErr: false,
},
{
name: "both set",
flags: map[string]string{"a": "x", "b": "y"},
check: []string{"a", "b"},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rt := newTestRuntime(tt.flags)
err := AtLeastOne(rt, tt.check...)
if (err != nil) != tt.wantErr {
t.Errorf("AtLeastOne() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestExactlyOne(t *testing.T) {
tests := []struct {
name string
flags map[string]string
check []string
wantErr bool
}{
{
name: "none set",
flags: map[string]string{"a": "", "b": ""},
check: []string{"a", "b"},
wantErr: true,
},
{
name: "one set",
flags: map[string]string{"a": "x", "b": ""},
check: []string{"a", "b"},
wantErr: false,
},
{
name: "both set",
flags: map[string]string{"a": "x", "b": "y"},
check: []string{"a", "b"},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rt := newTestRuntime(tt.flags)
err := ExactlyOne(rt, tt.check...)
if (err != nil) != tt.wantErr {
t.Errorf("ExactlyOne() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestParseIntBounded(t *testing.T) {
tests := []struct {
name string
val string
min, max int
want int
}{
{"within range", "10", 1, 50, 10},
{"below min", "0", 1, 50, 1},
{"above max", "100", 1, 50, 50},
{"at min", "1", 1, 50, 1},
{"at max", "50", 1, 50, 50},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := &cobra.Command{Use: "test"}
cmd.Flags().Int("page-size", 0, "")
cmd.ParseFlags(nil)
cmd.Flags().Set("page-size", tt.val)
rt := &RuntimeContext{Cmd: cmd}
got := ParseIntBounded(rt, "page-size", tt.min, tt.max)
if got != tt.want {
t.Errorf("ParseIntBounded() = %d, want %d", got, tt.want)
}
})
}
}
// ---------------------------------------------------------------------------
// ValidateSafePath — symlink escape prevention
// ---------------------------------------------------------------------------
// chdirForTest changes CWD to dir and restores the original CWD on cleanup.
func chdirForTest(t *testing.T, dir string) {
t.Helper()
orig, err := os.Getwd()
if err != nil {
t.Fatalf("Getwd: %v", err)
}
if err := os.Chdir(dir); err != nil {
t.Fatalf("Chdir(%q): %v", dir, err)
}
t.Cleanup(func() { os.Chdir(orig) })
}
// TestValidateSafePath_RejectsSymlinkEscape verifies that a relative path
// that resolves to a symlink pointing outside CWD is rejected.
func TestValidateSafePath_RejectsSymlinkEscape(t *testing.T) {
outside := t.TempDir() // target outside CWD
workDir := t.TempDir()
chdirForTest(t, workDir)
// Create a symlink inside CWD pointing to outside.
if err := os.Symlink(outside, filepath.Join(workDir, "evil_out")); err != nil {
t.Fatalf("Symlink: %v", err)
}
if err := ValidateSafePath(&localfileio.LocalFileIO{}, "evil_out"); err == nil {
t.Fatal("expected error for symlink pointing outside CWD, got nil")
}
}
// TestValidateSafePath_RejectsDanglingSymlink verifies that a dangling
// symlink (target does not exist) is rejected to prevent future escapes.
func TestValidateSafePath_RejectsDanglingSymlink(t *testing.T) {
workDir := t.TempDir()
chdirForTest(t, workDir)
if err := os.Symlink("/nonexistent/outside/target", filepath.Join(workDir, "dangling")); err != nil {
t.Fatalf("Symlink: %v", err)
}
if err := ValidateSafePath(&localfileio.LocalFileIO{}, "dangling"); err == nil {
t.Fatal("expected error for dangling symlink, got nil")
}
}
// TestValidateSafePath_AllowsNormalSubdir verifies that an existing real
// subdirectory within CWD is accepted.
func TestValidateSafePath_AllowsNormalSubdir(t *testing.T) {
workDir := t.TempDir()
chdirForTest(t, workDir)
subDir := filepath.Join(workDir, "output")
if err := os.Mkdir(subDir, 0700); err != nil {
t.Fatalf("Mkdir: %v", err)
}
if err := ValidateSafePath(&localfileio.LocalFileIO{}, "output"); err != nil {
t.Fatalf("expected no error for real subdir, got: %v", err)
}
}
// TestValidateSafePath_AllowsNonExistentPath verifies that a path that
// does not yet exist (new output directory) is accepted.
func TestValidateSafePath_AllowsNonExistentPath(t *testing.T) {
workDir := t.TempDir()
chdirForTest(t, workDir)
if err := ValidateSafePath(&localfileio.LocalFileIO{}, "new_output_dir"); err != nil {
t.Fatalf("expected no error for non-existent path, got: %v", err)
}
}