Files
larksuite__cli/shortcuts/apps/apps_observability_execute_test.go
木杉 1e87f67244 fix(apps): friendly-ize "Container not exists" for observability commands (#2302)
* 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.
2026-08-12 15:01:10 +08:00

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)
}
}