Files
dingtalk-workspace-cli/internal/transport/diagnostics_test.go

197 lines
6.2 KiB
Go

package transport
import (
"encoding/json"
"net/http"
"testing"
)
func TestExtractServerDiagnostics_SnakeCase(t *testing.T) {
t.Parallel()
data := json.RawMessage(`{"trace_id":"abc123","code":"PARAM_ERROR","technical_detail":"field required","friendly_hint":"请开通消息搜索权益","action_url":"https://example.test/enable-search","retryable":false}`)
diag := ExtractServerDiagnostics(data)
if diag.TraceID != "abc123" {
t.Fatalf("TraceID = %q, want abc123", diag.TraceID)
}
if diag.ServerErrorCode != "PARAM_ERROR" {
t.Fatalf("ServerErrorCode = %q, want PARAM_ERROR", diag.ServerErrorCode)
}
if diag.TechnicalDetail != "field required" {
t.Fatalf("TechnicalDetail = %q, want 'field required'", diag.TechnicalDetail)
}
if diag.FriendlyHint != "请开通消息搜索权益" {
t.Fatalf("FriendlyHint = %q, want search entitlement guidance", diag.FriendlyHint)
}
if diag.ActionURL != "https://example.test/enable-search" {
t.Fatalf("ActionURL = %q, want entitlement URL", diag.ActionURL)
}
if diag.ServerRetryable == nil || *diag.ServerRetryable != false {
t.Fatalf("ServerRetryable = %v, want false", diag.ServerRetryable)
}
}
func TestExtractServerDiagnostics_CamelCase(t *testing.T) {
t.Parallel()
data := json.RawMessage(`{"traceId":"xyz789","errorCode":"AUTH_ERROR","friendlyHint":"request access","actionUrl":"https://example.test/request"}`)
diag := ExtractServerDiagnostics(data)
if diag.TraceID != "xyz789" {
t.Fatalf("TraceID = %q, want xyz789", diag.TraceID)
}
if diag.ServerErrorCode != "AUTH_ERROR" {
t.Fatalf("ServerErrorCode = %q, want AUTH_ERROR", diag.ServerErrorCode)
}
if diag.FriendlyHint != "request access" || diag.ActionURL != "https://example.test/request" {
t.Fatalf("guidance = (%q, %q), want camelCase values", diag.FriendlyHint, diag.ActionURL)
}
}
func TestExtractServerDiagnostics_Empty(t *testing.T) {
t.Parallel()
diag := ExtractServerDiagnostics(nil)
if !diag.IsEmpty() {
t.Fatal("expected empty diagnostics for nil input")
}
diag = ExtractServerDiagnostics(json.RawMessage(`{}`))
if !diag.IsEmpty() {
t.Fatal("expected empty diagnostics for empty object")
}
}
func TestExtractServerDiagnostics_Malformed(t *testing.T) {
t.Parallel()
diag := ExtractServerDiagnostics(json.RawMessage(`not json`))
if !diag.IsEmpty() {
t.Fatal("expected empty diagnostics for malformed JSON")
}
}
func TestExtractServerDiagnosticsFromMap(t *testing.T) {
t.Parallel()
content := map[string]any{
"trace_id": "trace-001",
"code": "TIMEOUT_ERROR",
"technical_detail": "deadline exceeded",
"retryable": true,
}
diag := ExtractServerDiagnosticsFromMap(content)
if diag.TraceID != "trace-001" {
t.Fatalf("TraceID = %q, want trace-001", diag.TraceID)
}
if diag.ServerErrorCode != "TIMEOUT_ERROR" {
t.Fatalf("ServerErrorCode = %q, want TIMEOUT_ERROR", diag.ServerErrorCode)
}
if diag.ServerRetryable == nil || *diag.ServerRetryable != true {
t.Fatal("expected retryable=true")
}
}
func TestExtractServerDiagnosticsFromMapNestedBusinessCode(t *testing.T) {
t.Parallel()
content := map[string]any{
"trace_id": "trace-002",
"code": "BUSINESS_ERROR",
"result": map[string]any{
"success": false,
"errorCode": "ROBOT_NOT_FOUND",
"errorMsg": "robot info is not exist",
"friendly_hint": "请检查机器人是否仍在群内",
"action_url": "https://example.test/robots",
},
}
diag := ExtractServerDiagnosticsFromMap(content)
if diag.TraceID != "trace-002" {
t.Fatalf("TraceID = %q, want trace-002", diag.TraceID)
}
if diag.ServerErrorCode != "ROBOT_NOT_FOUND" {
t.Fatalf("ServerErrorCode = %q, want ROBOT_NOT_FOUND", diag.ServerErrorCode)
}
if diag.FriendlyHint != "请检查机器人是否仍在群内" || diag.ActionURL != "https://example.test/robots" {
t.Fatalf("nested guidance = (%q, %q), want nested values", diag.FriendlyHint, diag.ActionURL)
}
}
func TestExtractServerDiagnosticsFromMapNestedDiagnosticEnvelope(t *testing.T) {
t.Parallel()
content := map[string]any{
"result": map[string]any{
"data": map[string]any{
"requestId": "request-nested",
"technicalDetail": "nested detail",
"serverRetryable": false,
},
},
}
diag := ExtractServerDiagnosticsFromMap(content)
if diag.TraceID != "request-nested" || diag.TechnicalDetail != "nested detail" ||
diag.ServerRetryable == nil || *diag.ServerRetryable {
t.Fatalf("nested diagnostics = %#v", diag)
}
}
func TestCrossPlatformCoverageBoolDiagnosticsRecursiveEdges(t *testing.T) {
t.Parallel()
if value, ok := boolFromMapRecursive(nil, 0, "retryable"); ok || value {
t.Fatalf("nil recursive bool = (%v, %v)", value, ok)
}
if value, ok := boolFromMapRecursive(map[string]any{"retryable": true}, 9, "retryable"); ok || value {
t.Fatalf("over-depth recursive bool = (%v, %v)", value, ok)
}
content := map[string]any{
"result": []any{
"ignore",
map[string]any{"data": map[string]any{"serverRetryable": true}},
},
}
value, ok := boolFromMapRecursive(content, 0, "serverRetryable")
if !ok || !value {
t.Fatalf("nested array recursive bool = (%v, %v)", value, ok)
}
}
func TestExtractServerDiagnosticsFromMap_Empty(t *testing.T) {
t.Parallel()
diag := ExtractServerDiagnosticsFromMap(nil)
if !diag.IsEmpty() {
t.Fatal("expected empty for nil map")
}
}
func TestExtractTraceIDFromHeaders(t *testing.T) {
t.Parallel()
tests := []struct {
name string
headers http.Header
want string
}{
{"x-trace-id", http.Header{"X-Trace-Id": {"abc"}}, "abc"},
{"x-request-id", http.Header{"X-Request-Id": {"def"}}, "def"},
{"dingtalk", http.Header{"X-Dingtalk-Trace-Id": {"ghi"}}, "ghi"},
{"priority", http.Header{
"X-Trace-Id": {"first"},
"X-Dingtalk-Trace-Id": {"second"},
}, "first"},
{"empty", http.Header{}, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := ExtractTraceIDFromHeaders(tt.headers); got != tt.want {
t.Fatalf("got %q, want %q", got, tt.want)
}
})
}
}
func TestCoalesceStr(t *testing.T) {
t.Parallel()
if got := coalesceStr("", "b"); got != "b" {
t.Fatalf("got %q, want b", got)
}
if got := coalesceStr("a", "b"); got != "a" {
t.Fatalf("got %q, want a", got)
}
if got := coalesceStr("", ""); got != "" {
t.Fatalf("got %q, want empty", got)
}
}