Files
larksuite-cli/internal/output/print_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

102 lines
2.7 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package output
import (
"bytes"
"encoding/json"
"testing"
)
func TestPrintJson_InjectNotice_Map(t *testing.T) {
origNotice := PendingNotice
PendingNotice = func() map[string]interface{} {
return map[string]interface{}{"update": "available"}
}
defer func() { PendingNotice = origNotice }()
data := map[string]interface{}{"ok": true, "data": "test"}
var buf bytes.Buffer
PrintJson(&buf, data)
var got map[string]interface{}
if err := json.Unmarshal(buf.Bytes(), &got); err != nil {
t.Fatalf("failed to parse: %v", err)
}
notice, ok := got["_notice"].(map[string]interface{})
if !ok {
t.Fatal("expected _notice in map-based envelope")
}
if notice["update"] != "available" {
t.Errorf("expected update=available, got %v", notice["update"])
}
}
func TestPrintJson_InjectNotice_SkipsNonEnvelope(t *testing.T) {
origNotice := PendingNotice
PendingNotice = func() map[string]interface{} {
return map[string]interface{}{"update": "available"}
}
defer func() { PendingNotice = origNotice }()
// Map without "ok" key should not get _notice
data := map[string]interface{}{"name": "test"}
var buf bytes.Buffer
PrintJson(&buf, data)
var got map[string]interface{}
if err := json.Unmarshal(buf.Bytes(), &got); err != nil {
t.Fatalf("failed to parse: %v", err)
}
if _, ok := got["_notice"]; ok {
t.Error("expected no _notice for non-envelope map")
}
}
func TestPrintJson_Struct_PreservesNotice(t *testing.T) {
origNotice := PendingNotice
PendingNotice = nil // no global notice
defer func() { PendingNotice = origNotice }()
// Struct with Notice already set should preserve it
env := &Envelope{
OK: true,
Identity: "user",
Data: "hello",
Notice: map[string]interface{}{"update": "set-by-caller"},
}
var buf bytes.Buffer
PrintJson(&buf, env)
var got map[string]interface{}
if err := json.Unmarshal(buf.Bytes(), &got); err != nil {
t.Fatalf("failed to parse: %v", err)
}
notice, ok := got["_notice"].(map[string]interface{})
if !ok {
t.Fatal("expected _notice from struct field")
}
if notice["update"] != "set-by-caller" {
t.Errorf("expected update=set-by-caller, got %v", notice["update"])
}
}
func TestPrintJson_NoNotice(t *testing.T) {
origNotice := PendingNotice
PendingNotice = nil
defer func() { PendingNotice = origNotice }()
data := map[string]interface{}{"ok": true, "data": "test"}
var buf bytes.Buffer
PrintJson(&buf, data)
var got map[string]interface{}
if err := json.Unmarshal(buf.Bytes(), &got); err != nil {
t.Fatalf("failed to parse: %v", err)
}
if _, ok := got["_notice"]; ok {
t.Error("expected no _notice when PendingNotice is nil")
}
}