mirror of
https://github.com/larksuite/cli.git
synced 2026-07-08 10:08:02 +08:00
Merge pull request #1597 from larksuite/fix/delete-e2e
fix: remove unsed files
This commit is contained in:
@@ -1,632 +0,0 @@
|
||||
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package apps
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
clie2e "github.com/larksuite/cli/tests/cli_e2e"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
const (
|
||||
appsE2EAppID = "app_x"
|
||||
appsSecretValue = "super-secret-value-for-e2e"
|
||||
)
|
||||
|
||||
func TestAppsObservabilityDryRunContract(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
args []string
|
||||
method string
|
||||
url string
|
||||
assertBody func(*testing.T, string)
|
||||
}{
|
||||
{
|
||||
name: "log_list_request_shape",
|
||||
args: []string{
|
||||
"apps", "+log-list",
|
||||
"--app-id", appsE2EAppID,
|
||||
"--env", "online",
|
||||
"--level", "error",
|
||||
"--since", "2026-06-23T10:00:00Z",
|
||||
"--until", "2026-06-23T11:00:00Z",
|
||||
"--log-id", "LOG1",
|
||||
"--log-id", "LOG2",
|
||||
"--trace-id", "trace-1",
|
||||
"--keyword", "timeout",
|
||||
"--min-duration", "200",
|
||||
"--page-size", "50",
|
||||
"--page-token", "next-token",
|
||||
},
|
||||
method: "POST",
|
||||
url: "/open-apis/spark/v1/apps/app_x/search_logs",
|
||||
assertBody: func(t *testing.T, stdout string) {
|
||||
assert.Equal(t, "runtime", gjson.Get(stdout, "api.0.body.app_env").String(), "stdout:\n%s", stdout)
|
||||
assert.Equal(t, "1782208800000000000", gjson.Get(stdout, "api.0.body.start_timestamp_ns").String(), "stdout:\n%s", stdout)
|
||||
assert.Equal(t, "1782212400000000000", gjson.Get(stdout, "api.0.body.end_timestamp_ns").String(), "stdout:\n%s", stdout)
|
||||
assert.Equal(t, int64(50), gjson.Get(stdout, "api.0.body.limit").Int(), "stdout:\n%s", stdout)
|
||||
assert.Equal(t, "next-token", gjson.Get(stdout, "api.0.body.page_token").String(), "stdout:\n%s", stdout)
|
||||
requireStringArray(t, stdout, "api.0.body.filter.levels", []string{"ERROR"})
|
||||
requireStringArray(t, stdout, "api.0.body.filter.log_ids", []string{"LOG1", "LOG2"})
|
||||
requireStringArray(t, stdout, "api.0.body.filter.trace_ids", []string{"trace-1"})
|
||||
assert.Equal(t, "timeout", gjson.Get(stdout, "api.0.body.filter.keyword").String(), "stdout:\n%s", stdout)
|
||||
assert.Equal(t, int64(200), gjson.Get(stdout, "api.0.body.filter.min_duration_ms").Int(), "stdout:\n%s", stdout)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "log_get_uses_search_logs_with_limit_one",
|
||||
args: []string{
|
||||
"apps", "+log-get",
|
||||
"--app-id", appsE2EAppID,
|
||||
"--env", "online",
|
||||
"--log-id", "LOG763372528845174288",
|
||||
},
|
||||
method: "POST",
|
||||
url: "/open-apis/spark/v1/apps/app_x/search_logs",
|
||||
assertBody: func(t *testing.T, stdout string) {
|
||||
assert.Equal(t, "runtime", gjson.Get(stdout, "api.0.body.app_env").String(), "stdout:\n%s", stdout)
|
||||
assert.Equal(t, int64(1), gjson.Get(stdout, "api.0.body.limit").Int(), "stdout:\n%s", stdout)
|
||||
requireStringArray(t, stdout, "api.0.body.filter.log_ids", []string{"LOG763372528845174288"})
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "trace_list_request_shape",
|
||||
args: []string{
|
||||
"apps", "+trace-list",
|
||||
"--app-id", appsE2EAppID,
|
||||
"--env", "online",
|
||||
"--trace-id", "trace-1",
|
||||
"--root-span", "api-gateway",
|
||||
"--user-id", "ou_user",
|
||||
"--since", "2026-06-23T10:00:00Z",
|
||||
"--until", "2026-06-23T11:00:00Z",
|
||||
"--page-size", "25",
|
||||
"--page-token", "next-token",
|
||||
},
|
||||
method: "POST",
|
||||
url: "/open-apis/spark/v1/apps/app_x/search_traces",
|
||||
assertBody: func(t *testing.T, stdout string) {
|
||||
assert.Equal(t, "runtime", gjson.Get(stdout, "api.0.body.app_env").String(), "stdout:\n%s", stdout)
|
||||
assert.Equal(t, "1782208800000000000", gjson.Get(stdout, "api.0.body.start_timestamp_ns").String(), "stdout:\n%s", stdout)
|
||||
assert.Equal(t, "1782212400000000000", gjson.Get(stdout, "api.0.body.end_timestamp_ns").String(), "stdout:\n%s", stdout)
|
||||
assert.Equal(t, int64(25), gjson.Get(stdout, "api.0.body.limit").Int(), "stdout:\n%s", stdout)
|
||||
assert.Equal(t, "next-token", gjson.Get(stdout, "api.0.body.page_token").String(), "stdout:\n%s", stdout)
|
||||
requireStringArray(t, stdout, "api.0.body.filter.trace_ids", []string{"trace-1"})
|
||||
assert.Equal(t, "api-gateway", gjson.Get(stdout, "api.0.body.filter.keyword").String(), "stdout:\n%s", stdout)
|
||||
requireStringArray(t, stdout, "api.0.body.filter.user_ids", []string{"ou_user"})
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "trace_get_request_shape",
|
||||
args: []string{
|
||||
"apps", "+trace-get",
|
||||
"--app-id", appsE2EAppID,
|
||||
"--env", "online",
|
||||
"--trace-id", "359d7ab1d9e222b43ee56619a55f937a",
|
||||
},
|
||||
method: "POST",
|
||||
url: "/open-apis/spark/v1/apps/app_x/trace",
|
||||
assertBody: func(t *testing.T, stdout string) {
|
||||
assert.Equal(t, "runtime", gjson.Get(stdout, "api.0.body.app_env").String(), "stdout:\n%s", stdout)
|
||||
assert.Equal(t, "359d7ab1d9e222b43ee56619a55f937a", gjson.Get(stdout, "api.0.body.trace_id").String(), "stdout:\n%s", stdout)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "metric_query_request_shape",
|
||||
args: []string{
|
||||
"apps", "+metric-query",
|
||||
"--app-id", appsE2EAppID,
|
||||
"--env", "online",
|
||||
"--metric", "requests",
|
||||
"--series", "total",
|
||||
"--since", "2026-06-23T10:00:00Z",
|
||||
"--until", "2026-06-23T11:00:00Z",
|
||||
"--page", "/home",
|
||||
"--api", "/api/orders",
|
||||
"--down-sample", "1m",
|
||||
},
|
||||
method: "POST",
|
||||
url: "/open-apis/spark/v1/apps/app_x/query_metrics_data",
|
||||
assertBody: func(t *testing.T, stdout string) {
|
||||
assert.False(t, gjson.Get(stdout, "api.0.body.app_env").Exists(), "metric OpenAPI body should not include app_env, stdout:\n%s", stdout)
|
||||
assert.Equal(t, "1782208800", gjson.Get(stdout, "api.0.body.start_timestamp").String(), "stdout:\n%s", stdout)
|
||||
assert.Equal(t, "1782212400", gjson.Get(stdout, "api.0.body.end_timestamp").String(), "stdout:\n%s", stdout)
|
||||
requireStringArray(t, stdout, "api.0.body.metric_names", []string{"client_api_request_count"})
|
||||
assert.Equal(t, "/home", gjson.Get(stdout, "api.0.body.filter.pages.0").String(), "stdout:\n%s", stdout)
|
||||
assert.Equal(t, "/api/orders", gjson.Get(stdout, "api.0.body.filter.apis.0").String(), "stdout:\n%s", stdout)
|
||||
assert.Equal(t, "1m", gjson.Get(stdout, "api.0.body.down_sample").String(), "stdout:\n%s", stdout)
|
||||
assert.True(t, gjson.Get(stdout, "api.0.body.need_pack_lack_point").Exists(), "stdout:\n%s", stdout)
|
||||
assert.False(t, gjson.Get(stdout, "api.0.body.need_pack_lack_point").Bool(), "stdout:\n%s", stdout)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "analytics_query_request_shape",
|
||||
args: []string{
|
||||
"apps", "+analytics-query",
|
||||
"--app-id", appsE2EAppID,
|
||||
"--env", "online",
|
||||
"--analytics", "users",
|
||||
"--series", "active-users",
|
||||
"--since", "2026-06-23T10:00:00Z",
|
||||
"--until", "2026-06-23T11:00:00Z",
|
||||
"--page", "/home",
|
||||
"--device-type", "desktop",
|
||||
"--granularity", "week",
|
||||
},
|
||||
method: "POST",
|
||||
url: "/open-apis/spark/v1/apps/app_x/query_analytics_data",
|
||||
assertBody: func(t *testing.T, stdout string) {
|
||||
assert.False(t, gjson.Get(stdout, "api.0.body.app_env").Exists(), "analytics OpenAPI body should not include app_env, stdout:\n%s", stdout)
|
||||
assert.Equal(t, "1782208800000000000", gjson.Get(stdout, "api.0.body.start_timestamp_ns").String(), "stdout:\n%s", stdout)
|
||||
assert.Equal(t, "1782212400000000000", gjson.Get(stdout, "api.0.body.end_timestamp_ns").String(), "stdout:\n%s", stdout)
|
||||
requireStringArray(t, stdout, "api.0.body.metric_types", []string{"ACTIVE_USER"})
|
||||
assert.Equal(t, "WEEK", gjson.Get(stdout, "api.0.body.time_aggregation_unit").String(), "stdout:\n%s", stdout)
|
||||
assert.Equal(t, "/home", gjson.Get(stdout, "api.0.body.filter.page").String(), "stdout:\n%s", stdout)
|
||||
assert.Equal(t, "desktop", gjson.Get(stdout, "api.0.body.filter.device_types.0").String(), "stdout:\n%s", stdout)
|
||||
assert.True(t, gjson.Get(stdout, "api.0.body.need_pack_lack_point").Exists(), "stdout:\n%s", stdout)
|
||||
assert.False(t, gjson.Get(stdout, "api.0.body.need_pack_lack_point").Bool(), "stdout:\n%s", stdout)
|
||||
assert.False(t, gjson.Get(stdout, "api.0.body.group_by").Exists(), "group_by is intentionally unsupported for now, stdout:\n%s", stdout)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
result := runAppsDryRunCommand(t, ctx, tc.args, false)
|
||||
result.AssertExitCode(t, 0)
|
||||
assert.Equal(t, tc.method, gjson.Get(result.Stdout, "api.0.method").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.Equal(t, tc.url, gjson.Get(result.Stdout, "api.0.url").String(), "stdout:\n%s", result.Stdout)
|
||||
tc.assertBody(t, result.Stdout)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppsObservabilityRejectsNonOnlineEnv(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
args []string
|
||||
}{
|
||||
{
|
||||
name: "log_list",
|
||||
args: []string{"apps", "+log-list", "--app-id", appsE2EAppID, "--env", "dev"},
|
||||
},
|
||||
{
|
||||
name: "log_get",
|
||||
args: []string{"apps", "+log-get", "--app-id", appsE2EAppID, "--env", "dev", "--log-id", "LOG763372528845174288"},
|
||||
},
|
||||
{
|
||||
name: "trace_list",
|
||||
args: []string{"apps", "+trace-list", "--app-id", appsE2EAppID, "--env", "dev"},
|
||||
},
|
||||
{
|
||||
name: "trace_get",
|
||||
args: []string{"apps", "+trace-get", "--app-id", appsE2EAppID, "--env", "dev", "--trace-id", "359d7ab1d9e222b43ee56619a55f937a"},
|
||||
},
|
||||
{
|
||||
name: "metric_query",
|
||||
args: []string{"apps", "+metric-query", "--app-id", appsE2EAppID, "--env", "dev", "--metric", "requests"},
|
||||
},
|
||||
{
|
||||
name: "analytics_query",
|
||||
args: []string{"apps", "+analytics-query", "--app-id", appsE2EAppID, "--env", "dev", "--analytics", "users"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
result := runAppsDryRunCommand(t, ctx, tc.args, false)
|
||||
result.AssertExitCode(t, 2)
|
||||
raw := errorEnvelope(t, result)
|
||||
assert.Equal(t, "validation", gjson.Get(raw, "error.type").String(), "error envelope:\n%s", raw)
|
||||
assert.Equal(t, "invalid_argument", gjson.Get(raw, "error.subtype").String(), "error envelope:\n%s", raw)
|
||||
assert.Equal(t, "--env", gjson.Get(raw, "error.param").String(), "error envelope:\n%s", raw)
|
||||
assert.Contains(t, gjson.Get(raw, "error.message").String(), "observability commands only support online", "error envelope:\n%s", raw)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppsEnvVarDryRunAndSafety(t *testing.T) {
|
||||
t.Run("env_pull_uses_dev_post_body_contract", func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
projectDir := filepath.Join(t.TempDir(), "demo")
|
||||
|
||||
result := runAppsDryRunCommand(t, ctx, []string{
|
||||
"apps", "+env-pull",
|
||||
"--app-id", appsE2EAppID,
|
||||
"--project-path", projectDir,
|
||||
}, false)
|
||||
result.AssertExitCode(t, 0)
|
||||
assert.Equal(t, "POST", gjson.Get(result.Stdout, "api.0.method").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.Equal(t, "/open-apis/spark/v1/apps/app_x/env_vars", gjson.Get(result.Stdout, "api.0.url").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.Equal(t, "dev", gjson.Get(result.Stdout, "api.0.body.env").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.False(t, gjson.Get(result.Stdout, "api.0.params.include_values").Exists(), "stdout:\n%s", result.Stdout)
|
||||
assert.Equal(t, filepath.Join(projectDir, ".env.local"), gjson.Get(result.Stdout, "env_file").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.False(t, gjson.Get(result.Stdout, "env_keys").Exists(), "env-pull dry-run must not expose key list, stdout:\n%s", result.Stdout)
|
||||
assert.NotContains(t, result.Stdout, appsSecretValue, "env-pull dry-run must not leak env values, stdout:\n%s", result.Stdout)
|
||||
})
|
||||
|
||||
t.Run("envvar_list_defaults_to_dev_without_values", func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
result := runAppsDryRunCommand(t, ctx, []string{
|
||||
"apps", "+envvar-list",
|
||||
"--app-id", appsE2EAppID,
|
||||
}, false)
|
||||
result.AssertExitCode(t, 0)
|
||||
assert.Equal(t, "POST", gjson.Get(result.Stdout, "api.0.method").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.Equal(t, "/open-apis/spark/v1/apps/app_x/env_vars", gjson.Get(result.Stdout, "api.0.url").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.Equal(t, "dev", gjson.Get(result.Stdout, "api.0.body.env").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.False(t, gjson.Get(result.Stdout, "api.0.params.include_values").Exists(), "stdout:\n%s", result.Stdout)
|
||||
assert.False(t, gjson.Get(result.Stdout, "api.0.body.value").Exists(), "list dry-run must not send values, stdout:\n%s", result.Stdout)
|
||||
})
|
||||
|
||||
t.Run("envvar_set_dev_post_redacts_value", func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
result := runAppsDryRunCommand(t, ctx, []string{
|
||||
"apps", "+envvar-set",
|
||||
"--app-id", appsE2EAppID,
|
||||
"--env", "dev",
|
||||
"--key", "API_HOST",
|
||||
"--value", appsSecretValue,
|
||||
}, false)
|
||||
result.AssertExitCode(t, 0)
|
||||
assert.Equal(t, "POST", gjson.Get(result.Stdout, "api.0.method").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.Equal(t, "/open-apis/spark/v1/apps/app_x/create_or_update_env_var", gjson.Get(result.Stdout, "api.0.url").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.Equal(t, "dev", gjson.Get(result.Stdout, "api.0.body.env").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.Equal(t, "API_HOST", gjson.Get(result.Stdout, "api.0.body.key").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.NotContains(t, result.Stdout, appsSecretValue, "envvar-set dry-run must not leak raw value in stdout:\n%s", result.Stdout)
|
||||
assert.NotContains(t, result.Stderr, appsSecretValue, "envvar-set dry-run must not leak raw value in stderr:\n%s", result.Stderr)
|
||||
})
|
||||
|
||||
t.Run("envvar_set_online_dry_run_does_not_require_yes", func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
result := runAppsDryRunCommand(t, ctx, []string{
|
||||
"apps", "+envvar-set",
|
||||
"--app-id", appsE2EAppID,
|
||||
"--env", "online",
|
||||
"--key", "API_HOST",
|
||||
"--value", appsSecretValue,
|
||||
}, false)
|
||||
result.AssertExitCode(t, 0)
|
||||
assert.Equal(t, "POST", gjson.Get(result.Stdout, "api.0.method").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.Equal(t, "/open-apis/spark/v1/apps/app_x/create_or_update_env_var", gjson.Get(result.Stdout, "api.0.url").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.Equal(t, "online", gjson.Get(result.Stdout, "api.0.body.env").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.Equal(t, "API_HOST", gjson.Get(result.Stdout, "api.0.body.key").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.NotContains(t, result.Stdout, appsSecretValue, "online dry-run must not leak raw value in stdout:\n%s", result.Stdout)
|
||||
})
|
||||
|
||||
t.Run("envvar_set_online_requires_yes_without_dry_run", func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
result := runAppsCommand(t, ctx, []string{
|
||||
"apps", "+envvar-set",
|
||||
"--app-id", appsE2EAppID,
|
||||
"--env", "online",
|
||||
"--key", "API_HOST",
|
||||
"--value", appsSecretValue,
|
||||
}, false)
|
||||
result.AssertExitCode(t, 10)
|
||||
raw := errorEnvelope(t, result)
|
||||
assert.Equal(t, "confirmation", gjson.Get(raw, "error.type").String(), "error envelope:\n%s", raw)
|
||||
assert.Equal(t, "confirmation_required", gjson.Get(raw, "error.subtype").String(), "error envelope:\n%s", raw)
|
||||
assert.Contains(t, gjson.Get(raw, "error.hint").String(), "add --yes to confirm", "error envelope:\n%s", raw)
|
||||
assert.NotContains(t, raw, appsSecretValue, "confirmation error must not leak raw value:\n%s", raw)
|
||||
})
|
||||
|
||||
t.Run("envvar_delete_dry_run_body", func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
result := runAppsDryRunCommand(t, ctx, []string{
|
||||
"apps", "+envvar-delete",
|
||||
"--app-id", appsE2EAppID,
|
||||
"--env", "dev",
|
||||
"--key", "API_HOST",
|
||||
"--key", "API_TOKEN",
|
||||
}, true)
|
||||
result.AssertExitCode(t, 0)
|
||||
assert.Equal(t, "POST", gjson.Get(result.Stdout, "api.0.method").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.Equal(t, "/open-apis/spark/v1/apps/app_x/delete_env_vars", gjson.Get(result.Stdout, "api.0.url").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.Equal(t, "dev", gjson.Get(result.Stdout, "api.0.body.env").String(), "stdout:\n%s", result.Stdout)
|
||||
requireStringArray(t, result.Stdout, "api.0.body.keys", []string{"API_HOST", "API_TOKEN"})
|
||||
assert.False(t, gjson.Get(result.Stdout, "api.0.body.value").Exists(), "delete body must not contain values, stdout:\n%s", result.Stdout)
|
||||
})
|
||||
|
||||
t.Run("envvar_delete_requires_yes_without_dry_run", func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
result := runAppsCommand(t, ctx, []string{
|
||||
"apps", "+envvar-delete",
|
||||
"--app-id", appsE2EAppID,
|
||||
"--env", "dev",
|
||||
"--key", "API_HOST",
|
||||
}, false)
|
||||
result.AssertExitCode(t, 10)
|
||||
raw := errorEnvelope(t, result)
|
||||
assert.Equal(t, "confirmation", gjson.Get(raw, "error.type").String(), "error envelope:\n%s", raw)
|
||||
assert.Equal(t, "confirmation_required", gjson.Get(raw, "error.subtype").String(), "error envelope:\n%s", raw)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAppsObservabilityLiveFixtureOutputs(t *testing.T) {
|
||||
appID := os.Getenv("LARK_CLI_E2E_APPS_OBSERVABILITY_APP_ID")
|
||||
logID := os.Getenv("LARK_CLI_E2E_APPS_LOG_ID")
|
||||
traceID := os.Getenv("LARK_CLI_E2E_APPS_TRACE_ID")
|
||||
if appID == "" || logID == "" || traceID == "" {
|
||||
t.Skip("FIXTURE: Set LARK_CLI_E2E_APPS_OBSERVABILITY_APP_ID, LARK_CLI_E2E_APPS_LOG_ID, and LARK_CLI_E2E_APPS_TRACE_ID to an online app with visible log, trace, metric, and analytics data")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
t.Run("log_get_returns_fixture_log", func(t *testing.T) {
|
||||
result := runAppsLiveCommand(t, ctx, []string{
|
||||
"apps", "+log-get",
|
||||
"--app-id", appID,
|
||||
"--env", "online",
|
||||
"--log-id", logID,
|
||||
}, false)
|
||||
result.AssertExitCode(t, 0)
|
||||
result.AssertStdoutStatus(t, true)
|
||||
assert.Equal(t, logID, gjson.Get(result.Stdout, "data.log_id").String(), "stdout:\n%s", result.Stdout)
|
||||
})
|
||||
|
||||
t.Run("trace_get_returns_fixture_trace", func(t *testing.T) {
|
||||
result := runAppsLiveCommand(t, ctx, []string{
|
||||
"apps", "+trace-get",
|
||||
"--app-id", appID,
|
||||
"--env", "online",
|
||||
"--trace-id", traceID,
|
||||
}, false)
|
||||
result.AssertExitCode(t, 0)
|
||||
result.AssertStdoutStatus(t, true)
|
||||
assert.Equal(t, traceID, gjson.Get(result.Stdout, "data.trace_id").String(), "stdout:\n%s", result.Stdout)
|
||||
require.NotEmpty(t, gjson.Get(result.Stdout, "data.spans").Array(), "trace should include spans, stdout:\n%s", result.Stdout)
|
||||
})
|
||||
|
||||
t.Run("metric_query_returns_request_series", func(t *testing.T) {
|
||||
result := runAppsLiveCommand(t, ctx, []string{
|
||||
"apps", "+metric-query",
|
||||
"--app-id", appID,
|
||||
"--env", "online",
|
||||
"--metric", "requests",
|
||||
"--series", "total",
|
||||
}, false)
|
||||
result.AssertExitCode(t, 0)
|
||||
result.AssertStdoutStatus(t, true)
|
||||
items := gjson.Get(result.Stdout, "data.items").Array()
|
||||
require.NotEmpty(t, items, "fixture app should have request metric points, stdout:\n%s", result.Stdout)
|
||||
assert.True(t, items[0].Get("values.total").Exists(), "request metric should expose total values, stdout:\n%s", result.Stdout)
|
||||
})
|
||||
|
||||
t.Run("analytics_query_returns_active_users", func(t *testing.T) {
|
||||
result := runAppsLiveCommand(t, ctx, []string{
|
||||
"apps", "+analytics-query",
|
||||
"--app-id", appID,
|
||||
"--env", "online",
|
||||
"--analytics", "users",
|
||||
"--series", "active-users",
|
||||
}, false)
|
||||
result.AssertExitCode(t, 0)
|
||||
result.AssertStdoutStatus(t, true)
|
||||
items := gjson.Get(result.Stdout, "data.items").Array()
|
||||
require.NotEmpty(t, items, "fixture app should have analytics points, stdout:\n%s", result.Stdout)
|
||||
assert.True(t, items[0].Get("values.active-users").Exists(), "analytics should expose active-users values, stdout:\n%s", result.Stdout)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAppsEnvVarLiveWorkflow(t *testing.T) {
|
||||
appID := os.Getenv("LARK_CLI_E2E_APPS_ENVVAR_APP_ID")
|
||||
if appID == "" {
|
||||
t.Skip("FIXTURE: Set LARK_CLI_E2E_APPS_ENVVAR_APP_ID to an app where the user identity may create, list, and delete online env vars")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
suffix := strings.NewReplacer("-", "_").Replace(clie2e.GenerateSuffix())
|
||||
key := "LARK_CLI_E2E_" + suffix
|
||||
value := "secret-value-" + suffix
|
||||
created := false
|
||||
|
||||
t.Cleanup(func() {
|
||||
if !created {
|
||||
return
|
||||
}
|
||||
cleanupCtx, cleanupCancel := clie2e.CleanupContext()
|
||||
defer cleanupCancel()
|
||||
deleteResult, deleteErr := clie2e.RunCmd(cleanupCtx, clie2e.Request{
|
||||
Args: []string{
|
||||
"apps", "+envvar-delete",
|
||||
"--app-id", appID,
|
||||
"--env", "online",
|
||||
"--key", key,
|
||||
},
|
||||
DefaultAs: "user",
|
||||
Env: appsNoNoticeEnv(),
|
||||
Yes: true,
|
||||
})
|
||||
clie2e.ReportCleanupFailure(t, "delete apps envvar "+key, deleteResult, deleteErr)
|
||||
})
|
||||
|
||||
t.Run("set_online_redacts_value", func(t *testing.T) {
|
||||
result := runAppsLiveCommand(t, ctx, []string{
|
||||
"apps", "+envvar-set",
|
||||
"--app-id", appID,
|
||||
"--env", "online",
|
||||
"--key", key,
|
||||
"--value", value,
|
||||
}, true)
|
||||
if result.ExitCode == 0 {
|
||||
created = true
|
||||
}
|
||||
result.AssertExitCode(t, 0)
|
||||
result.AssertStdoutStatus(t, true)
|
||||
assert.Equal(t, key, gjson.Get(result.Stdout, "data.key").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.Equal(t, "online", gjson.Get(result.Stdout, "data.env").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.Contains(t, []string{"set", "created", "updated"}, gjson.Get(result.Stdout, "data.action").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.NotContains(t, result.Stdout, value, "set output must not leak raw value, stdout:\n%s", result.Stdout)
|
||||
assert.NotContains(t, result.Stderr, value, "set output must not leak raw value, stderr:\n%s", result.Stderr)
|
||||
})
|
||||
|
||||
t.Run("list_include_values_observes_created_key", func(t *testing.T) {
|
||||
result, err := clie2e.RunCmdWithRetry(ctx, clie2e.Request{
|
||||
Args: []string{
|
||||
"apps", "+envvar-list",
|
||||
"--app-id", appID,
|
||||
"--env", "online",
|
||||
"--include-values",
|
||||
},
|
||||
DefaultAs: "user",
|
||||
Env: appsNoNoticeEnv(),
|
||||
}, clie2e.RetryOptions{
|
||||
ShouldRetry: func(result *clie2e.Result) bool {
|
||||
return result == nil || result.ExitCode != 0 || !envVarKeyExists(result.Stdout, key)
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
result.AssertExitCode(t, 0)
|
||||
result.AssertStdoutStatus(t, true)
|
||||
item, found := envVarItem(result.Stdout, key)
|
||||
require.True(t, found, "list should include created key %q, stdout:\n%s", key, result.Stdout)
|
||||
assert.Equal(t, "online", item.Get("env").String(), "stdout:\n%s", result.Stdout)
|
||||
assert.Equal(t, value, item.Get("value").String(), "include-values should expose the explicitly requested test value, stdout:\n%s", result.Stdout)
|
||||
})
|
||||
|
||||
t.Run("delete_removes_key", func(t *testing.T) {
|
||||
deleteResult := runAppsLiveCommand(t, ctx, []string{
|
||||
"apps", "+envvar-delete",
|
||||
"--app-id", appID,
|
||||
"--env", "online",
|
||||
"--key", key,
|
||||
}, true)
|
||||
if deleteResult.ExitCode == 0 {
|
||||
created = false
|
||||
}
|
||||
deleteResult.AssertExitCode(t, 0)
|
||||
deleteResult.AssertStdoutStatus(t, true)
|
||||
requireStringArray(t, deleteResult.Stdout, "data.deleted_keys", []string{key})
|
||||
assert.Equal(t, "online", gjson.Get(deleteResult.Stdout, "data.env").String(), "stdout:\n%s", deleteResult.Stdout)
|
||||
|
||||
listResult, err := clie2e.RunCmdWithRetry(ctx, clie2e.Request{
|
||||
Args: []string{
|
||||
"apps", "+envvar-list",
|
||||
"--app-id", appID,
|
||||
"--env", "online",
|
||||
"--include-values",
|
||||
},
|
||||
DefaultAs: "user",
|
||||
Env: appsNoNoticeEnv(),
|
||||
}, clie2e.RetryOptions{
|
||||
ShouldRetry: func(result *clie2e.Result) bool {
|
||||
return result == nil || result.ExitCode != 0 || envVarKeyExists(result.Stdout, key)
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
listResult.AssertExitCode(t, 0)
|
||||
listResult.AssertStdoutStatus(t, true)
|
||||
assert.False(t, envVarKeyExists(listResult.Stdout, key), "deleted key should be absent, stdout:\n%s", listResult.Stdout)
|
||||
})
|
||||
}
|
||||
|
||||
func runAppsDryRunCommand(t *testing.T, ctx context.Context, args []string, yes bool) *clie2e.Result {
|
||||
t.Helper()
|
||||
dryRunArgs := append([]string{}, args...)
|
||||
dryRunArgs = append(dryRunArgs, "--dry-run")
|
||||
return runAppsCommandWithEnv(t, ctx, dryRunArgs, yes, appsDryRunEnv())
|
||||
}
|
||||
|
||||
func runAppsCommand(t *testing.T, ctx context.Context, args []string, yes bool) *clie2e.Result {
|
||||
t.Helper()
|
||||
return runAppsCommandWithEnv(t, ctx, args, yes, appsDryRunEnv())
|
||||
}
|
||||
|
||||
func runAppsLiveCommand(t *testing.T, ctx context.Context, args []string, yes bool) *clie2e.Result {
|
||||
t.Helper()
|
||||
return runAppsCommandWithEnv(t, ctx, args, yes, appsNoNoticeEnv())
|
||||
}
|
||||
|
||||
func runAppsCommandWithEnv(t *testing.T, ctx context.Context, args []string, yes bool, env map[string]string) *clie2e.Result {
|
||||
t.Helper()
|
||||
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
||||
Args: args,
|
||||
DefaultAs: "user",
|
||||
Env: env,
|
||||
Yes: yes,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return result
|
||||
}
|
||||
|
||||
func appsDryRunEnv() map[string]string {
|
||||
env := appsNoNoticeEnv()
|
||||
env["LARKSUITE_CLI_APP_ID"] = "cli-e2e-app-id"
|
||||
env["LARKSUITE_CLI_APP_SECRET"] = "cli-e2e-app-secret"
|
||||
env["LARKSUITE_CLI_BRAND"] = "feishu"
|
||||
return env
|
||||
}
|
||||
|
||||
func appsNoNoticeEnv() map[string]string {
|
||||
return map[string]string{
|
||||
"LARKSUITE_CLI_NO_UPDATE_NOTIFIER": "1",
|
||||
"LARKSUITE_CLI_NO_SKILLS_NOTIFIER": "1",
|
||||
}
|
||||
}
|
||||
|
||||
func requireStringArray(t *testing.T, stdout string, path string, want []string) {
|
||||
t.Helper()
|
||||
got := gjson.Get(stdout, path).Array()
|
||||
require.Len(t, got, len(want), "path %s should contain %d items, stdout:\n%s", path, len(want), stdout)
|
||||
for i, value := range want {
|
||||
assert.Equal(t, value, got[i].String(), "path %s[%d], stdout:\n%s", path, i, stdout)
|
||||
}
|
||||
}
|
||||
|
||||
func errorEnvelope(t *testing.T, result *clie2e.Result) string {
|
||||
t.Helper()
|
||||
raw := strings.TrimSpace(result.Stdout)
|
||||
if raw == "" {
|
||||
raw = strings.TrimSpace(result.Stderr)
|
||||
}
|
||||
require.NotEmpty(t, raw, "expected structured error output, stdout:\n%s\nstderr:\n%s", result.Stdout, result.Stderr)
|
||||
return raw
|
||||
}
|
||||
|
||||
func envVarKeyExists(stdout string, key string) bool {
|
||||
_, found := envVarItem(stdout, key)
|
||||
return found
|
||||
}
|
||||
|
||||
func envVarItem(stdout string, key string) (gjson.Result, bool) {
|
||||
for _, item := range gjson.Get(stdout, "data.items").Array() {
|
||||
if item.Get("key").String() == key {
|
||||
return item, true
|
||||
}
|
||||
}
|
||||
return gjson.Result{}, false
|
||||
}
|
||||
Reference in New Issue
Block a user