mirror of
https://github.com/larksuite/cli.git
synced 2026-07-08 10:08:02 +08:00
246 lines
6.5 KiB
Go
246 lines
6.5 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package secplugin
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/larksuite/cli/internal/envvars"
|
|
)
|
|
|
|
// unsetEnv clears key for the duration of the test and restores its original value.
|
|
func unsetEnv(t *testing.T, key string) {
|
|
t.Helper()
|
|
old, had := os.LookupEnv(key)
|
|
_ = os.Unsetenv(key)
|
|
t.Cleanup(func() {
|
|
if had {
|
|
_ = os.Setenv(key, old)
|
|
} else {
|
|
_ = os.Unsetenv(key)
|
|
}
|
|
})
|
|
}
|
|
|
|
// unsetSecPluginEnv clears SEC-related environment variables for deterministic tests.
|
|
func unsetSecPluginEnv(t *testing.T) {
|
|
t.Helper()
|
|
unsetEnv(t, envvars.CliSecEnable)
|
|
unsetEnv(t, envvars.CliSecProxy)
|
|
unsetEnv(t, envvars.CliSecCA)
|
|
unsetEnv(t, envvars.CliSecAuth)
|
|
}
|
|
|
|
// writeFile creates parent directories and writes test data for fixtures.
|
|
func writeFile(t *testing.T, path string, data []byte, perm os.FileMode) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
|
|
t.Fatalf("MkdirAll: %v", err)
|
|
}
|
|
if err := os.WriteFile(path, data, perm); err != nil {
|
|
t.Fatalf("WriteFile: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestLoad_MissingFileReturnsNil verifies that Load reports no config when no file
|
|
// or SEC environment overrides exist.
|
|
func TestLoad_MissingFileReturnsNil(t *testing.T) {
|
|
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
|
loadOnce = sync.Once{}
|
|
loadCfg = nil
|
|
loadErr = nil
|
|
unsetSecPluginEnv(t)
|
|
// TestLoad_MissingFileReturnsNil must reset loadOnce, loadCfg, and loadErr
|
|
// because multiple tests in this package share the package-level Load()
|
|
// cache via sync.Once.
|
|
cfg, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("Load() error = %v", err)
|
|
}
|
|
if cfg != nil {
|
|
t.Fatalf("Load() = %#v, want nil (missing file)", cfg)
|
|
}
|
|
}
|
|
|
|
// TestApplyToTransport_SetsProxy verifies that a valid SEC config installs a fixed proxy.
|
|
func TestApplyToTransport_SetsProxy(t *testing.T) {
|
|
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
|
loadOnce = sync.Once{}
|
|
loadCfg = nil
|
|
loadErr = nil
|
|
unsetSecPluginEnv(t)
|
|
|
|
cfgPath := Path()
|
|
writeFile(t, cfgPath, []byte(`{
|
|
"LARKSUITE_CLI_SEC_ENABLE": true,
|
|
"LARKSUITE_CLI_SEC_PROXY": "http://127.0.0.1:3128",
|
|
"LARKSUITE_CLI_SEC_CA": "",
|
|
"LARKSUITE_CLI_SEC_AUTH": false
|
|
}`), 0600)
|
|
|
|
cfg, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("Load() error = %v", err)
|
|
}
|
|
if cfg == nil || !cfg.Enabled() {
|
|
t.Fatalf("cfg.Enabled() = %v, want true", cfg)
|
|
}
|
|
|
|
base := http.DefaultTransport.(*http.Transport)
|
|
tr, err := cfg.ApplyToTransport(base)
|
|
if err != nil {
|
|
t.Fatalf("ApplyToTransport() error = %v", err)
|
|
}
|
|
if tr.Proxy == nil {
|
|
t.Fatal("Proxy func is nil, want fixed proxy")
|
|
}
|
|
u, err := tr.Proxy(&http.Request{URL: &url.URL{Scheme: "https", Host: "open.feishu.cn"}})
|
|
if err != nil {
|
|
t.Fatalf("Proxy() error = %v", err)
|
|
}
|
|
if u == nil || u.String() != "http://127.0.0.1:3128" {
|
|
t.Fatalf("Proxy() = %v, want http://127.0.0.1:3128", u)
|
|
}
|
|
}
|
|
|
|
// TestLoad_RejectsNonLoopbackProxy verifies that SEC mode rejects non-loopback proxies.
|
|
func TestLoad_RejectsNonLoopbackProxy(t *testing.T) {
|
|
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
|
loadOnce = sync.Once{}
|
|
loadCfg = nil
|
|
loadErr = nil
|
|
unsetSecPluginEnv(t)
|
|
|
|
cfgPath := Path()
|
|
writeFile(t, cfgPath, []byte(`{
|
|
"LARKSUITE_CLI_SEC_ENABLE": true,
|
|
"LARKSUITE_CLI_SEC_PROXY": "http://10.0.0.1:3128",
|
|
"LARKSUITE_CLI_SEC_CA": "",
|
|
"LARKSUITE_CLI_SEC_AUTH": false
|
|
}`), 0600)
|
|
|
|
cfg, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("Load() error = %v", err)
|
|
}
|
|
if cfg == nil || !cfg.Enabled() {
|
|
t.Fatalf("cfg.Enabled() = %v, want true", cfg)
|
|
}
|
|
_, err = cfg.ApplyToTransport(http.DefaultTransport.(*http.Transport))
|
|
if err == nil {
|
|
t.Fatal("ApplyToTransport() error = nil, want invalid proxy host error")
|
|
}
|
|
}
|
|
|
|
// TestConfig_ProxyURLRejectsUnsupportedParts verifies the SEC proxy validator
|
|
// rejects URLs with missing ports, queries, and fragments.
|
|
func TestConfig_ProxyURLRejectsUnsupportedParts(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
raw string
|
|
want string
|
|
}{
|
|
{
|
|
name: "missing explicit port",
|
|
raw: "http://127.0.0.1",
|
|
want: "explicit port is required",
|
|
},
|
|
{
|
|
name: "query string",
|
|
raw: "http://127.0.0.1:3128?foo=bar",
|
|
want: "query is not allowed",
|
|
},
|
|
{
|
|
name: "fragment",
|
|
raw: "http://127.0.0.1:3128#frag",
|
|
want: "fragment is not allowed",
|
|
},
|
|
}
|
|
|
|
for _, tt := range cases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, err := (&Config{Proxy: tt.raw}).proxyURL()
|
|
if err == nil {
|
|
t.Fatalf("proxyURL() error = nil, want substring %q", tt.want)
|
|
}
|
|
if !strings.Contains(err.Error(), tt.want) {
|
|
t.Fatalf("proxyURL() error = %q, want substring %q", err, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestLoad_EnvOnlyConfig verifies that SEC settings can come entirely from environment variables.
|
|
func TestLoad_EnvOnlyConfig(t *testing.T) {
|
|
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
|
loadOnce = sync.Once{}
|
|
loadCfg = nil
|
|
loadErr = nil
|
|
|
|
t.Setenv(envvars.CliSecEnable, "true")
|
|
t.Setenv(envvars.CliSecProxy, "http://127.0.0.1:7777")
|
|
t.Setenv(envvars.CliSecCA, "")
|
|
t.Setenv(envvars.CliSecAuth, "true")
|
|
|
|
cfg, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("Load() error = %v", err)
|
|
}
|
|
if cfg == nil || !cfg.Enabled() {
|
|
t.Fatalf("cfg.Enabled() = %v, want true", cfg)
|
|
}
|
|
if !cfg.AuthEnabled() {
|
|
t.Fatalf("cfg.AuthEnabled() = false, want true")
|
|
}
|
|
tr, err := cfg.ApplyToTransport(http.DefaultTransport.(*http.Transport))
|
|
if err != nil {
|
|
t.Fatalf("ApplyToTransport() error = %v", err)
|
|
}
|
|
u, err := tr.Proxy(&http.Request{URL: &url.URL{Scheme: "https", Host: "open.feishu.cn"}})
|
|
if err != nil {
|
|
t.Fatalf("Proxy() error = %v", err)
|
|
}
|
|
if u == nil || u.String() != "http://127.0.0.1:7777" {
|
|
t.Fatalf("Proxy() = %v, want http://127.0.0.1:7777", u)
|
|
}
|
|
}
|
|
|
|
// TestLoad_EnvOverridesFile verifies that SEC environment variables override file values.
|
|
func TestLoad_EnvOverridesFile(t *testing.T) {
|
|
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
|
loadOnce = sync.Once{}
|
|
loadCfg = nil
|
|
loadErr = nil
|
|
|
|
// File enables with one proxy.
|
|
cfgPath := Path()
|
|
writeFile(t, cfgPath, []byte(`{
|
|
"LARKSUITE_CLI_SEC_ENABLE": true,
|
|
"LARKSUITE_CLI_SEC_PROXY": "http://127.0.0.1:3128",
|
|
"LARKSUITE_CLI_SEC_CA": "",
|
|
"LARKSUITE_CLI_SEC_AUTH": false
|
|
}`), 0600)
|
|
|
|
// Env overrides: disable + different proxy (should be irrelevant once disabled).
|
|
t.Setenv(envvars.CliSecEnable, "false")
|
|
t.Setenv(envvars.CliSecProxy, "http://127.0.0.1:9999")
|
|
|
|
cfg, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("Load() error = %v", err)
|
|
}
|
|
if cfg == nil {
|
|
t.Fatalf("Load() = nil, want non-nil (file exists)")
|
|
}
|
|
if cfg.Enabled() {
|
|
t.Fatalf("cfg.Enabled() = true, want false (env override)")
|
|
}
|
|
}
|