Prefer schema --compact in agent docs, remove服务发现/cache teaching, and stop doctor from reporting a no-op cache health item. Co-authored-by: Cursor <cursoragent@cursor.com>
254 lines
6.4 KiB
Go
254 lines
6.4 KiB
Go
// Copyright 2026 Alibaba Group
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/keychain"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition"
|
|
)
|
|
|
|
func TestCountResults(t *testing.T) {
|
|
checks := []checkResult{
|
|
{Status: statusPass},
|
|
{Status: statusPass},
|
|
{Status: statusWarn},
|
|
{Status: statusFail},
|
|
}
|
|
pass, warn, fail := countResults(checks)
|
|
if pass != 2 || warn != 1 || fail != 1 {
|
|
t.Errorf("expected (2,1,1), got (%d,%d,%d)", pass, warn, fail)
|
|
}
|
|
}
|
|
|
|
func TestCountResultsAllPass(t *testing.T) {
|
|
checks := []checkResult{
|
|
{Status: statusPass},
|
|
{Status: statusPass},
|
|
}
|
|
pass, warn, fail := countResults(checks)
|
|
if pass != 2 || warn != 0 || fail != 0 {
|
|
t.Errorf("expected (2,0,0), got (%d,%d,%d)", pass, warn, fail)
|
|
}
|
|
}
|
|
|
|
func TestStatusIcon(t *testing.T) {
|
|
tests := []struct {
|
|
status checkStatus
|
|
want string
|
|
}{
|
|
{statusPass, "✅"},
|
|
{statusWarn, "⚠️"},
|
|
{statusFail, "❌"},
|
|
}
|
|
for _, tc := range tests {
|
|
got := statusIcon(tc.status)
|
|
if got != tc.want {
|
|
t.Errorf("statusIcon(%q) = %q, want %q", tc.status, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPrintCheckResult(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
r := checkResult{
|
|
Name: "test",
|
|
Status: statusFail,
|
|
Message: "something broke",
|
|
Hint: "try fixing it",
|
|
}
|
|
printCheckResult(&buf, r)
|
|
|
|
out := buf.String()
|
|
if !strings.Contains(out, "❌") {
|
|
t.Error("expected fail icon")
|
|
}
|
|
if !strings.Contains(out, "something broke") {
|
|
t.Error("expected message")
|
|
}
|
|
if !strings.Contains(out, "try fixing it") {
|
|
t.Error("expected hint")
|
|
}
|
|
}
|
|
|
|
func TestPrintCheckResultNoHint(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
r := checkResult{
|
|
Name: "test",
|
|
Status: statusPass,
|
|
Message: "all good",
|
|
}
|
|
printCheckResult(&buf, r)
|
|
|
|
out := buf.String()
|
|
if !strings.Contains(out, "✅") {
|
|
t.Error("expected pass icon")
|
|
}
|
|
lines := strings.Split(strings.TrimSpace(out), "\n")
|
|
if len(lines) != 1 {
|
|
t.Errorf("expected 1 line (no hint), got %d", len(lines))
|
|
}
|
|
}
|
|
|
|
func TestDoctorCheckAuthReportsKeychainUnavailable(t *testing.T) {
|
|
t.Setenv("DWS_CONFIG_DIR", filepath.Join(t.TempDir(), "config"))
|
|
|
|
prev := edition.Get()
|
|
edition.Override(&edition.Hooks{
|
|
LoadToken: func(configDir string) ([]byte, error) {
|
|
return nil, keychain.NewUnavailableError("read DEK from macOS Keychain", errors.New("default keychain missing"))
|
|
},
|
|
})
|
|
t.Cleanup(func() {
|
|
edition.Override(prev)
|
|
})
|
|
|
|
var buf bytes.Buffer
|
|
r := doctorCheckAuth(context.Background(), &buf, false)
|
|
|
|
if r.Name != "auth" {
|
|
t.Fatalf("name = %q, want auth", r.Name)
|
|
}
|
|
if r.Status != statusFail {
|
|
t.Fatalf("status = %q, want fail", r.Status)
|
|
}
|
|
if !strings.Contains(r.Message, "Keychain") && !strings.Contains(r.Message, "钥匙串") {
|
|
t.Fatalf("message should mention Keychain/钥匙串; result=%+v", r)
|
|
}
|
|
if !strings.Contains(r.Hint, keychain.DisableKeychainEnv) {
|
|
t.Fatalf("hint should mention %s; result=%+v", keychain.DisableKeychainEnv, r)
|
|
}
|
|
}
|
|
|
|
func TestDoctorCheckAuthReportsDEKMissing(t *testing.T) {
|
|
t.Setenv("DWS_CONFIG_DIR", filepath.Join(t.TempDir(), "config"))
|
|
|
|
prev := edition.Get()
|
|
edition.Override(&edition.Hooks{
|
|
LoadToken: func(configDir string) ([]byte, error) {
|
|
return nil, fmt.Errorf("load from keychain: %w", keychain.ErrDEKMissing)
|
|
},
|
|
})
|
|
t.Cleanup(func() {
|
|
edition.Override(prev)
|
|
})
|
|
|
|
var buf bytes.Buffer
|
|
r := doctorCheckAuth(context.Background(), &buf, false)
|
|
|
|
if r.Name != "auth" {
|
|
t.Fatalf("name = %q, want auth", r.Name)
|
|
}
|
|
if r.Status != statusFail {
|
|
t.Fatalf("status = %q, want fail", r.Status)
|
|
}
|
|
if !strings.Contains(r.Message, "登录密钥") {
|
|
t.Fatalf("message should mention 登录密钥; result=%+v", r)
|
|
}
|
|
if !strings.Contains(r.Hint, "重新登录") {
|
|
t.Fatalf("hint should mention 重新登录; result=%+v", r)
|
|
}
|
|
detail, ok := r.Detail.(map[string]string)
|
|
if !ok || detail["reason"] != "dek_missing" {
|
|
t.Fatalf("detail = %#v, want reason=dek_missing", r.Detail)
|
|
}
|
|
}
|
|
|
|
func TestDoctorCheckKeychainReportsUnavailable(t *testing.T) {
|
|
prev := doctorKeychainDiagnose
|
|
doctorKeychainDiagnose = func() keychain.Diagnostic {
|
|
return keychain.Diagnostic{
|
|
OK: false,
|
|
Reason: "keychain_unavailable",
|
|
Message: "macOS 默认钥匙串不存在",
|
|
Hint: "恢复默认钥匙串后重试",
|
|
Detail: map[string]string{
|
|
"default_keychain": "/tmp/missing.keychain-db",
|
|
},
|
|
}
|
|
}
|
|
t.Cleanup(func() {
|
|
doctorKeychainDiagnose = prev
|
|
})
|
|
|
|
var buf bytes.Buffer
|
|
r := doctorCheckKeychain(&buf, false)
|
|
|
|
if r.Name != "keychain" {
|
|
t.Fatalf("name = %q, want keychain", r.Name)
|
|
}
|
|
if r.Status != statusFail {
|
|
t.Fatalf("status = %q, want fail", r.Status)
|
|
}
|
|
if r.Message != "macOS 默认钥匙串不存在" {
|
|
t.Fatalf("message = %q", r.Message)
|
|
}
|
|
if r.Hint == "" {
|
|
t.Fatalf("hint is empty; result=%+v", r)
|
|
}
|
|
detail, ok := r.Detail.(map[string]string)
|
|
if !ok || detail["default_keychain"] == "" {
|
|
t.Fatalf("detail = %#v, want default_keychain", r.Detail)
|
|
}
|
|
}
|
|
|
|
func TestDoctorCommandStructure(t *testing.T) {
|
|
cmd := newDoctorCommand()
|
|
if cmd.Use != "doctor" {
|
|
t.Errorf("Use = %q, want doctor", cmd.Use)
|
|
}
|
|
|
|
jsonFlag := cmd.Flags().Lookup("json")
|
|
if jsonFlag == nil {
|
|
t.Error("expected --json flag")
|
|
}
|
|
timeoutFlag := cmd.Flags().Lookup("timeout")
|
|
if timeoutFlag == nil {
|
|
t.Error("expected --timeout flag")
|
|
}
|
|
}
|
|
|
|
func TestCheckResultJSONMarshal(t *testing.T) {
|
|
r := checkResult{
|
|
Name: "auth",
|
|
Status: statusPass,
|
|
Message: "已登录",
|
|
}
|
|
data, err := json.Marshal(r)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var parsed map[string]any
|
|
if err := json.Unmarshal(data, &parsed); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if parsed["name"] != "auth" {
|
|
t.Errorf("expected name=auth, got %v", parsed["name"])
|
|
}
|
|
if parsed["status"] != "pass" {
|
|
t.Errorf("expected status=pass, got %v", parsed["status"])
|
|
}
|
|
if _, hasHint := parsed["hint"]; hasHint {
|
|
t.Error("empty hint should be omitted")
|
|
}
|
|
}
|