mirror of
https://github.com/larksuite/cli.git
synced 2026-07-03 14:02:43 +08:00
183 lines
6.2 KiB
Go
183 lines
6.2 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package base
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/larksuite/cli/internal/output"
|
|
)
|
|
|
|
func TestErrorDetailHelpers(t *testing.T) {
|
|
if value, ok := nonNilMapValue(nil, "error"); ok || value != nil {
|
|
t.Fatalf("nil map should not return value")
|
|
}
|
|
if value, ok := nonNilMapValue(map[string]interface{}{"error": nil}, "error"); ok || value != nil {
|
|
t.Fatalf("nil entry should not return value")
|
|
}
|
|
detail := map[string]interface{}{"message": "boom", "hint": "retry later"}
|
|
if value, ok := nonNilMapValue(map[string]interface{}{"error": detail}, "error"); !ok || value == nil {
|
|
t.Fatalf("expected non-nil detail")
|
|
}
|
|
if got := extractErrorDetail(map[string]interface{}{"error": detail}); got == nil {
|
|
t.Fatalf("expected root detail")
|
|
}
|
|
if got := extractErrorDetail(map[string]interface{}{"data": map[string]interface{}{"error": detail}}); got == nil {
|
|
t.Fatalf("expected nested detail")
|
|
}
|
|
if got := extractErrorHint(map[string]interface{}{"data": map[string]interface{}{"error": detail}}); got != "retry later" {
|
|
t.Fatalf("hint=%q", got)
|
|
}
|
|
if got := extractDataErrorMessage(map[string]interface{}{"data": map[string]interface{}{"error": detail}}); got != "boom" {
|
|
t.Fatalf("message=%q", got)
|
|
}
|
|
if got := extractDataErrorMessage(map[string]interface{}{"data": map[string]interface{}{}}); got != "" {
|
|
t.Fatalf("message=%q", got)
|
|
}
|
|
}
|
|
|
|
func TestHandleBaseAPIResultErrorPaths(t *testing.T) {
|
|
if _, err := handleBaseAPIResultAny(nil, assertErr{}, "list fields"); err == nil || !strings.Contains(err.Error(), "list fields") {
|
|
t.Fatalf("err=%v", err)
|
|
}
|
|
result := map[string]interface{}{
|
|
"code": 190001,
|
|
"msg": "bad request",
|
|
"data": map[string]interface{}{
|
|
"error": map[string]interface{}{"message": "invalid filter", "hint": "check field name"},
|
|
},
|
|
}
|
|
if _, err := handleBaseAPIResultAny(result, nil, "set filter"); err == nil || !strings.Contains(err.Error(), "invalid filter") {
|
|
t.Fatalf("err=%v", err)
|
|
} else {
|
|
var exitErr *output.ExitError
|
|
if !errors.As(err, &exitErr) || exitErr.Detail == nil || exitErr.Detail.Code != 190001 {
|
|
t.Fatalf("expected structured code 190001, got %v", err)
|
|
}
|
|
}
|
|
if _, err := handleBaseAPIResult(result, nil, "set filter"); err == nil {
|
|
t.Fatalf("expected error")
|
|
}
|
|
}
|
|
|
|
func TestHandleBaseAPIResultCleansBaseErrorDetail(t *testing.T) {
|
|
result := map[string]interface{}{
|
|
"code": 800010407,
|
|
"msg": "cell value invalid",
|
|
"data": map[string]interface{}{
|
|
"error": map[string]interface{}{
|
|
"docs_url": nil,
|
|
"hint": "Provide a number value.",
|
|
"level": "error",
|
|
"logid": "20260508160000000000000000000000",
|
|
"message": "The cell value does not match the expected input shape.",
|
|
"path": "Amount",
|
|
"retry_after_ms": nil,
|
|
"retryable": false,
|
|
"extra_context": "future detail field",
|
|
"table": map[string]interface{}{"id": "tbl_1", "name": "Orders"},
|
|
"type": "invalid_request",
|
|
"upstream_code": nil,
|
|
"value": "abc",
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := handleBaseAPIResultAny(result, nil, "API call failed")
|
|
var exitErr *output.ExitError
|
|
if !errors.As(err, &exitErr) || exitErr.Detail == nil {
|
|
t.Fatalf("expected structured exit error, got %v", err)
|
|
}
|
|
|
|
errDetail := exitErr.Detail
|
|
if errDetail.Code != 800010407 {
|
|
t.Fatalf("code=%d", errDetail.Code)
|
|
}
|
|
if errDetail.Hint != "Provide a number value." {
|
|
t.Fatalf("hint=%q", errDetail.Hint)
|
|
}
|
|
detail, _ := errDetail.Detail.(map[string]interface{})
|
|
if detail == nil {
|
|
t.Fatalf("expected cleaned detail, got %#v", errDetail.Detail)
|
|
}
|
|
if _, exists := detail["message"]; exists {
|
|
t.Fatalf("detail should not repeat message: %#v", detail)
|
|
}
|
|
if _, exists := detail["hint"]; exists {
|
|
t.Fatalf("detail should not repeat hint: %#v", detail)
|
|
}
|
|
if _, exists := detail["docs_url"]; exists {
|
|
t.Fatalf("detail should omit nil docs_url: %#v", detail)
|
|
}
|
|
if detail["level"] != "error" {
|
|
t.Fatalf("detail should preserve non-duplicate fields: %#v", detail)
|
|
}
|
|
if detail["extra_context"] != "future detail field" {
|
|
t.Fatalf("detail should pass through unknown non-nil fields: %#v", detail)
|
|
}
|
|
if detail["path"] != "Amount" || detail["value"] != "abc" {
|
|
t.Fatalf("cleaned detail mismatch: %#v", detail)
|
|
}
|
|
if detail["logid"] != "20260508160000000000000000000000" {
|
|
t.Fatalf("logid=%q", detail["logid"])
|
|
}
|
|
if retryable, ok := detail["retryable"].(bool); !ok || retryable {
|
|
t.Fatalf("retryable=%v", detail["retryable"])
|
|
}
|
|
table, _ := detail["table"].(map[string]interface{})
|
|
if table["id"] != "tbl_1" || table["name"] != "Orders" {
|
|
t.Fatalf("table=%#v", detail["table"])
|
|
}
|
|
}
|
|
|
|
func TestHandleBaseAPIResultAlwaysRemovesMessageAndHintFromDetail(t *testing.T) {
|
|
result := map[string]interface{}{
|
|
"code": output.LarkErrTokenNoPermission,
|
|
"msg": "permission denied",
|
|
"data": map[string]interface{}{
|
|
"error": map[string]interface{}{
|
|
"hint": "Grant base:record:read to the app.",
|
|
"message": "Missing required scope base:record:read.",
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := handleBaseAPIResultAny(result, nil, "API call failed")
|
|
var exitErr *output.ExitError
|
|
if !errors.As(err, &exitErr) || exitErr.Detail == nil {
|
|
t.Fatalf("expected structured exit error, got %v", err)
|
|
}
|
|
if exitErr.Detail.Message != "Permission denied [99991676]" {
|
|
t.Fatalf("message=%q", exitErr.Detail.Message)
|
|
}
|
|
if exitErr.Detail.Detail != nil {
|
|
t.Fatalf("detail should be empty after removing message and hint: %#v", exitErr.Detail.Detail)
|
|
}
|
|
}
|
|
|
|
func TestAttachBaseResponseLogIDFromHeader(t *testing.T) {
|
|
result := map[string]interface{}{
|
|
"code": 91402,
|
|
"msg": "NOTEXIST",
|
|
"data": map[string]interface{}{},
|
|
}
|
|
attachBaseErrorLogID(result, "20260508170000000000000000000000")
|
|
|
|
_, err := handleBaseAPIResultAny(result, nil, "API call failed")
|
|
var exitErr *output.ExitError
|
|
if !errors.As(err, &exitErr) || exitErr.Detail == nil {
|
|
t.Fatalf("expected structured exit error, got %v", err)
|
|
}
|
|
detail, _ := exitErr.Detail.Detail.(map[string]interface{})
|
|
if detail["logid"] != "20260508170000000000000000000000" {
|
|
t.Fatalf("logid=%q", detail["logid"])
|
|
}
|
|
}
|
|
|
|
type assertErr struct{}
|
|
|
|
func (assertErr) Error() string { return "network timeout" }
|