Files
larksuite-cli/internal/output/errors_test.go
MaxHuang22 27139a0919 feat: add automatic CLI update detection and notification (#144)
Add non-blocking update check that queries the npm registry for the
latest @larksuite/cli version. Results are cached locally (24h TTL)
to avoid repeated network requests.

When a newer version is detected, a `_notice.update` field is injected
into all JSON output envelopes (success, error, and shortcut responses),
enabling AI agents and scripts to surface upgrade prompts.

Key changes:
- New `internal/update` package: registry fetch, semver compare, cache
- Async check in root command (cache-first, then background refresh)
- `_notice` field added to Envelope/ErrorEnvelope structs
- `PrintJson` injects notice into map-based envelopes with "ok" key
- `doctor` command gains cli_version and cli_update checks
- Suppressed for CI, DEV builds, shell completion, and git-describe versions
2026-03-31 19:01:39 +08:00

151 lines
3.5 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package output
import (
"bytes"
"encoding/json"
"fmt"
"testing"
)
func TestMarkRaw_ExitError(t *testing.T) {
err := ErrAPI(99991672, "API error: [99991672] scope not enabled", nil)
if err.Raw {
t.Fatal("expected Raw=false before MarkRaw")
}
result := MarkRaw(err)
if result != err {
t.Error("expected MarkRaw to return the same error")
}
if !err.Raw {
t.Error("expected Raw=true after MarkRaw")
}
}
func TestMarkRaw_NonExitError(t *testing.T) {
plain := fmt.Errorf("some plain error")
result := MarkRaw(plain)
if result != plain {
t.Error("expected MarkRaw to return the same error for non-ExitError")
}
}
func TestMarkRaw_Nil(t *testing.T) {
result := MarkRaw(nil)
if result != nil {
t.Error("expected MarkRaw(nil) to return nil")
}
}
func TestWriteErrorEnvelope_WithNotice(t *testing.T) {
// Set up PendingNotice
origNotice := PendingNotice
PendingNotice = func() map[string]interface{} {
return map[string]interface{}{
"update": map[string]interface{}{
"current": "1.0.0",
"latest": "2.0.0",
},
}
}
defer func() { PendingNotice = origNotice }()
exitErr := &ExitError{
Code: 1,
Detail: &ErrDetail{Type: "api_error", Message: "something failed"},
}
var buf bytes.Buffer
WriteErrorEnvelope(&buf, exitErr, "user")
var env map[string]interface{}
if err := json.Unmarshal(buf.Bytes(), &env); err != nil {
t.Fatalf("failed to parse output: %v", err)
}
// Verify _notice is present
notice, ok := env["_notice"].(map[string]interface{})
if !ok {
t.Fatal("expected _notice field in output")
}
update, ok := notice["update"].(map[string]interface{})
if !ok {
t.Fatal("expected _notice.update field")
}
if update["latest"] != "2.0.0" {
t.Errorf("expected latest=2.0.0, got %v", update["latest"])
}
// Verify standard fields
if env["ok"] != false {
t.Error("expected ok=false")
}
if env["identity"] != "user" {
t.Errorf("expected identity=user, got %v", env["identity"])
}
}
func TestWriteErrorEnvelope_WithoutNotice(t *testing.T) {
// Ensure PendingNotice is nil
origNotice := PendingNotice
PendingNotice = nil
defer func() { PendingNotice = origNotice }()
exitErr := &ExitError{
Code: 1,
Detail: &ErrDetail{Type: "api_error", Message: "something failed"},
}
var buf bytes.Buffer
WriteErrorEnvelope(&buf, exitErr, "bot")
var env map[string]interface{}
if err := json.Unmarshal(buf.Bytes(), &env); err != nil {
t.Fatalf("failed to parse output: %v", err)
}
if _, ok := env["_notice"]; ok {
t.Error("expected no _notice field when PendingNotice is nil")
}
}
func TestWriteErrorEnvelope_NilDetail(t *testing.T) {
exitErr := &ExitError{Code: 1}
var buf bytes.Buffer
WriteErrorEnvelope(&buf, exitErr, "user")
if buf.Len() != 0 {
t.Errorf("expected no output for nil Detail, got: %s", buf.String())
}
}
func TestGetNotice(t *testing.T) {
// Nil PendingNotice → nil
origNotice := PendingNotice
PendingNotice = nil
if got := GetNotice(); got != nil {
t.Errorf("expected nil, got %v", got)
}
// With PendingNotice → returns value
PendingNotice = func() map[string]interface{} {
return map[string]interface{}{"update": "test"}
}
got := GetNotice()
if got == nil || got["update"] != "test" {
t.Errorf("expected {update: test}, got %v", got)
}
// PendingNotice returns nil → nil
PendingNotice = func() map[string]interface{} { return nil }
if got := GetNotice(); got != nil {
t.Errorf("expected nil, got %v", got)
}
PendingNotice = origNotice
}