Files
Shiny 340dd241c9 feat: 实现完整的本地配置管理与CLI重构
本次重构实现了标准化的本地配置系统,替换原有的硬编码环境变量读取逻辑:
1. 新增跨平台的原子化配置文件读写,支持Unix和Windows系统
2. 新增init命令用于安全初始化和更新本地凭据
3. 替换原有错误提示文案为更友好的中文提示
4. 更新文档说明新的配置流程和安全规范
5. 新增完整的配置相关测试用例
6. 添加必要的依赖包支持
2026-08-01 02:39:49 +08:00

104 lines
3.2 KiB
Go

package command
import (
"bytes"
"errors"
"os"
"path/filepath"
"strings"
"testing"
dto "code.zhecent.com/open/shop-crm-agent/internal/api"
"google.golang.org/protobuf/encoding/protojson"
)
func TestRootCommandExposesOnlyCrmOperations(t *testing.T) {
cmd := New("test-version")
want := map[string]bool{
"init": true,
"customer-list": true, "project": true,
"quotation": true, "catalog-search": true, "edit": true,
}
for _, child := range cmd.Commands() {
if child.Name() == "credential" {
t.Fatal("external client exposes credential administration")
}
if child.Name() == "progress-template-list" {
t.Fatal("external client exposes retired progress template query")
}
delete(want, child.Name())
}
if len(want) != 0 {
t.Fatalf("missing root commands = %v", want)
}
if cmd.Version != "test-version" {
t.Fatalf("version = %q", cmd.Version)
}
}
func TestProjectCommandsExcludeProgressAndTemplateFlag(t *testing.T) {
root := New("test-version")
project, _, err := root.Find([]string{"project"})
if err != nil {
t.Fatal(err)
}
for _, child := range project.Commands() {
if child.Name() == "progress-complete" {
t.Fatal("project command exposes retired progress completion")
}
}
for _, name := range []string{"create", "update"} {
command, _, findErr := root.Find([]string{"project", name})
if findErr != nil {
t.Fatal(findErr)
}
if command.Flags().Lookup("progress-template-key") != nil {
t.Fatalf("project %s exposes retired progress template flag", name)
}
}
}
func TestRemoteCommandRequiresCredentialConfiguration(t *testing.T) {
t.Setenv("LIGHTCORE_API_BASE_URL", "")
t.Setenv("LIGHTCORE_SHOP_CRM_AGENT_TOKEN", "")
repoRoot := t.TempDir()
if err := os.WriteFile(filepath.Join(repoRoot, "go.mod"), []byte("module code.zhecent.com/open/shop-crm-agent\n"), 0o600); err != nil {
t.Fatal(err)
}
t.Chdir(repoRoot)
cmd := New("test")
cmd.SetArgs([]string{"customer-list"})
if err := cmd.Execute(); err == nil || !strings.Contains(err.Error(), "shop-crm-agent init") {
t.Fatalf("error = %v", err)
}
}
func TestWriteCrmAgentResultPrintsStructuredBusinessError(t *testing.T) {
commandErr := errors.New("CRM Agent API 错误 400: 报价已锁定")
response := &dto.ProjectResponse{Header: &dto.ResponseHeader{
Code: 400, Message: "报价已锁定", RequestId: "request-123", Replayed: true,
}}
var output bytes.Buffer
err := writeCrmAgentResult(&output, response, commandErr)
if !errors.Is(err, commandErr) {
t.Fatalf("command error = %v", err)
}
printed := &dto.ProjectResponse{}
if err := protojson.Unmarshal(output.Bytes(), printed); err != nil {
t.Fatalf("decode output: %v", err)
}
if printed.GetHeader() == nil || printed.GetHeader().Code != 400 || printed.GetHeader().RequestId != "request-123" || !printed.GetHeader().Replayed {
t.Fatalf("printed header = %#v", printed.GetHeader())
}
}
func TestWriteCrmAgentResultSkipsMissingTransportResponse(t *testing.T) {
commandErr := errors.New("connection refused")
var output bytes.Buffer
err := writeCrmAgentResult(&output, &dto.ProjectResponse{}, commandErr)
if !errors.Is(err, commandErr) || output.Len() != 0 {
t.Fatalf("error=%v output=%q", err, output.String())
}
}