mirror of
https://github.com/larksuite/cli.git
synced 2026-09-14 18:42:53 +08:00
1e87f67244
* fix(apps): friendly-ize "Container not exists" for observability commands
+metric-list and +analytics-list passed the upstream business code 400002655
("Container not exists") through verbatim. The message reads like an
infrastructure fault and misleads callers (including AI agents) into retrying a
non-retryable, expected business state: an app with no running container simply
has no metrics/analytics to query yet.
Rewrite it at a scoped observability helper (withObservabilityHint) into a
user-facing explanation plus a deploy-then-retry next step, mirroring the
existing isAppNoDatabaseError override. Detection is code-OR-message so a server
renumber alone does not silently drop the rewrite. Classification, code, and the
wrapped cause are preserved; unrelated failures still fall through to the shared
app-id recovery hint (and its own no-database override).
* test(apps): add execution-path regression tests for observability container hint
common_test.go proves withObservabilityHint in isolation but stays green if a
call site reverts to withAppsHint. Drive +metric-list and +analytics-list
Execute with a mocked 400002655 "Container not exists" envelope and assert the
container-specific message/hint/code, so a revert fails the build. Also closes
the two uncovered call-site lines flagged by coverage.
* fix(errclass): classify no-container code as validation/failed_precondition
Register 400002655 in sparkCodeMeta mirroring its no-database twin
(400002465) so both "expected precondition not met" business states expose
the same validation/failed_precondition classification to machine consumers,
instead of falling back to api/unknown. The shortcut-layer message rewrite
already keyed off the raw code, so this only aligns the typed envelope's
category/subtype; update the execution-path tests to pin the new
classification.
* fix(apps): gate the no-container hint's release behind user authorization
The no-container hint told a harness to deploy via +release-create, a "write"
that takes the whole app live and can affect existing production traffic —
without the user-confirmation gate its no-database twin deliberately carries.
Since the hint's audience is an AI agent that acts on it, a failed metrics read
could trigger an unconfirmed go-live. Lead with a read-only +release-list
status check and gate +release-create behind an explicit user confirmation,
mirroring appNoDatabaseHint.
67 lines
2.5 KiB
Go
67 lines
2.5 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package apps
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/larksuite/cli/internal/httpmock"
|
|
)
|
|
|
|
// The two observability commands must route their API failures through
|
|
// withObservabilityHint, not the generic withAppsHint. common_test.go proves the
|
|
// helper in isolation, but that stays green if a call site reverts to
|
|
// withAppsHint — so these command-level tests drive the real Execute path with a
|
|
// mocked "Container not exists" (400002655) envelope and assert the
|
|
// container-specific rewrite. A revert to withAppsHint would leave the raw
|
|
// message and swap in the app-id hint, failing both assertions.
|
|
|
|
func TestAppsMetricList_NoContainerRewriteThroughExecute(t *testing.T) {
|
|
factory, stdout, reg := newAppsExecuteFactory(t)
|
|
reg.Register(&httpmock.Stub{
|
|
Method: "POST",
|
|
URL: metricListPath("app_x"),
|
|
Body: map[string]interface{}{"code": appNoContainerCode, "msg": "Container not exists"},
|
|
})
|
|
|
|
err := runAppsShortcut(t, AppsMetricList, []string{
|
|
"+metric-list", "--app-id", "app_x", "--metric", "requests", "--as", "user",
|
|
}, factory, stdout)
|
|
|
|
p := requireAppsValidationProblem(t, err)
|
|
if p.Code != appNoContainerCode {
|
|
t.Errorf("Code = %d, want %d", p.Code, appNoContainerCode)
|
|
}
|
|
if p.Message != appNoContainerMessage {
|
|
t.Errorf("Message = %q, want container rewrite %q (call site may have reverted to withAppsHint)", p.Message, appNoContainerMessage)
|
|
}
|
|
if p.Hint != appNoContainerHint {
|
|
t.Errorf("Hint = %q, want deploy hint %q (call site may have reverted to withAppsHint)", p.Hint, appNoContainerHint)
|
|
}
|
|
}
|
|
|
|
func TestAppsAnalyticsList_NoContainerRewriteThroughExecute(t *testing.T) {
|
|
factory, stdout, reg := newAppsExecuteFactory(t)
|
|
reg.Register(&httpmock.Stub{
|
|
Method: "POST",
|
|
URL: analyticsListPath("app_x"),
|
|
Body: map[string]interface{}{"code": appNoContainerCode, "msg": "Container not exists"},
|
|
})
|
|
|
|
err := runAppsShortcut(t, AppsAnalyticsList, []string{
|
|
"+analytics-list", "--app-id", "app_x", "--analytics", "users", "--as", "user",
|
|
}, factory, stdout)
|
|
|
|
p := requireAppsValidationProblem(t, err)
|
|
if p.Code != appNoContainerCode {
|
|
t.Errorf("Code = %d, want %d", p.Code, appNoContainerCode)
|
|
}
|
|
if p.Message != appNoContainerMessage {
|
|
t.Errorf("Message = %q, want container rewrite %q (call site may have reverted to withAppsHint)", p.Message, appNoContainerMessage)
|
|
}
|
|
if p.Hint != appNoContainerHint {
|
|
t.Errorf("Hint = %q, want deploy hint %q (call site may have reverted to withAppsHint)", p.Hint, appNoContainerHint)
|
|
}
|
|
}
|