Files
wehub-resource-sync bf9395e022
CI / results (push) Blocked by required conditions
CI / license-header (push) Has been skipped
CI / e2e-dry-run (push) Has been skipped
CI / e2e-live (push) Waiting to run
CI / fast-gate (push) Failing after 0s
Test PR Label Logic / test-pr-labels (push) Failing after 1s
Skill Format Check / check-format (push) Failing after 2s
CI / security (push) Failing after 5s
CI / unit-test (push) Has been skipped
CI / lint (push) Has been skipped
CI / script-test (push) Has been skipped
CI / deterministic-gate (push) Has been skipped
CI / coverage (push) Has been skipped
CI / deadcode (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 12:22:54 +08:00

665 lines
22 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package apps
import (
"encoding/json"
"strings"
"testing"
"time"
"github.com/larksuite/cli/internal/httpmock"
)
func TestAppsLogList_DryRunBuildsSearchLogsBody(t *testing.T) {
factory, stdout, _ := newAppsExecuteFactory(t)
err := runAppsShortcut(t, AppsLogList, []string{
"+log-list", "--app-id", "app_x", "--level", "error",
"--trace-id", "trace-1",
"--keyword", "timeout", "--module", "frontend", "--user-id", "ou_1",
"--page", "/home", "--api", "/api/orders", "--min-duration", "200",
"--since", "2026-06-23T10:00:00Z", "--until", "2026-06-23T10:01:00Z",
"--page-size", "20", "--dry-run", "--as", "user",
}, factory, stdout)
if err != nil {
t.Fatalf("dry-run err=%v", err)
}
var env struct {
API []struct {
Method string `json:"method"`
URL string `json:"url"`
Body map[string]interface{} `json:"body"`
} `json:"api"`
}
if err := json.Unmarshal(stdout.Bytes(), &env); err != nil {
t.Fatalf("decode dry-run: %v\n%s", err, stdout.String())
}
if env.API[0].Method != "POST" || env.API[0].URL != "/open-apis/spark/v1/apps/app_x/search_logs" {
t.Fatalf("method/url = %s %s", env.API[0].Method, env.API[0].URL)
}
if env.API[0].Body["app_env"] != "runtime" || env.API[0].Body["limit"] != float64(20) {
t.Fatalf("body = %#v", env.API[0].Body)
}
filter := env.API[0].Body["filter"].(map[string]interface{})
if got := filter["keyword"]; got != "timeout" {
t.Fatalf("filter.keyword = %v", got)
}
for key, want := range map[string]string{
"modules": "frontend",
"user_ids": "ou_1",
"pages": "/home",
"apis": "/api/orders",
} {
values, ok := filter[key].([]interface{})
if !ok || len(values) != 1 || values[0] != want {
t.Fatalf("filter.%s = %#v, want [%q]", key, filter[key], want)
}
}
if env.API[0].Body["start_timestamp_ns"] != "1782208800000000000" ||
env.API[0].Body["end_timestamp_ns"] != "1782208860000000000" {
t.Fatalf("timestamps = %#v %#v", env.API[0].Body["start_timestamp_ns"], env.API[0].Body["end_timestamp_ns"])
}
}
func TestAppsLogList_DoesNotAcceptLogIDFlag(t *testing.T) {
factory, stdout, _ := newAppsExecuteFactory(t)
err := runAppsShortcut(t, AppsLogList, []string{
"+log-list", "--app-id", "app_x", "--log-id", "LOG1", "--as", "user",
}, factory, stdout)
if err == nil || !strings.Contains(err.Error(), "unknown flag: --log-id") {
t.Fatalf("expected unknown --log-id flag, got %v", err)
}
}
func TestAppsLogList_RejectsDevEnv(t *testing.T) {
factory, stdout, _ := newAppsExecuteFactory(t)
err := runAppsShortcut(t, AppsLogList, []string{"+log-list", "--app-id", "app_x", "--environment", "dev", "--as", "user"}, factory, stdout)
requireAppsValidationParam(t, err, "--environment")
}
func TestAppsLogGet_SearchesByLogIDLimitOne(t *testing.T) {
factory, stdout, reg := newAppsExecuteFactory(t)
stub := &httpmock.Stub{
Method: "POST",
URL: "/open-apis/spark/v1/apps/app_x/search_logs",
Body: map[string]interface{}{
"code": 0,
"data": map[string]interface{}{
"log_items": []interface{}{
map[string]interface{}{"log_id": "LOG1", "level": "INFO"},
},
},
},
}
reg.Register(stub)
if err := runAppsShortcut(t, AppsLogGet, []string{"+log-get", "--app-id", "app_x", "--log-id", "LOG1", "--as", "user"}, factory, stdout); err != nil {
t.Fatalf("execute err=%v", err)
}
var sent map[string]interface{}
if err := json.Unmarshal(stub.CapturedBody, &sent); err != nil {
t.Fatal(err)
}
if sent["limit"] != float64(1) {
t.Fatalf("limit = %v, want 1", sent["limit"])
}
if sent["app_env"] != "runtime" {
t.Fatalf("app_env = %v, want runtime", sent["app_env"])
}
}
func TestAppsLogGet_AcceptsDataArraySearchResponse(t *testing.T) {
factory, stdout, reg := newAppsExecuteFactory(t)
search := &httpmock.Stub{
Method: "POST",
URL: "/open-apis/spark/v1/apps/app_x/search_logs",
RawBody: []byte(`{
"code": 0,
"data": [
{
"log_id": "LOG7655249917057764881",
"level": "ERROR",
"attributes": {
"commit_id": "commit_array",
"source_map_file_prefix": "sourcemaps/array",
"frames": [{"file":"main.js","line":10,"column":20}]
}
}
]
}`),
}
resolve := &httpmock.Stub{
Method: "POST",
URL: "/open-apis/spark/v1/apps/app_x/resolve_stack_trace",
Body: map[string]interface{}{
"code": 0,
"data": map[string]interface{}{
"source_stack": []interface{}{
map[string]interface{}{"file": "src/App.tsx", "line": 7, "column": 9},
},
},
},
}
reg.Register(search)
reg.Register(resolve)
if err := runAppsShortcut(t, AppsLogGet, []string{"+log-get", "--app-id", "app_x", "--log-id", "LOG7655249917057764881", "--as", "user"}, factory, stdout); err != nil {
t.Fatalf("execute err=%v", err)
}
if got := stdout.String(); !strings.Contains(got, `"source_stack_status": "resolved"`) || !strings.Contains(got, "src/App.tsx") {
t.Fatalf("stdout missing resolved source stack from data array response: %s", got)
}
}
func TestAppsLogList_NormalizesResponseVariantsAndCanonicalLevel(t *testing.T) {
factory, stdout, reg := newAppsExecuteFactory(t)
reg.Register(&httpmock.Stub{
Method: "POST",
URL: "/open-apis/spark/v1/apps/app_x/search_logs",
Body: map[string]interface{}{
"code": 0,
"data": map[string]interface{}{
"logItems": []interface{}{
map[string]interface{}{
"id": "LOG1",
"traceID": "trace-1",
"timestampNs": "1782209472123456789",
"severityText": "ERROR",
},
},
"nextPageToken": "tok-next",
"hasMore": true,
},
},
})
if err := runAppsShortcut(t, AppsLogList, []string{"+log-list", "--app-id", "app_x", "--as", "user"}, factory, stdout); err != nil {
t.Fatalf("execute err=%v", err)
}
var env struct {
Data struct {
Items []map[string]interface{} `json:"items"`
PageToken string `json:"page_token"`
HasMore bool `json:"has_more"`
} `json:"data"`
}
if err := json.Unmarshal(stdout.Bytes(), &env); err != nil {
t.Fatalf("decode output: %v\n%s", err, stdout.String())
}
if env.Data.PageToken != "tok-next" || !env.Data.HasMore {
t.Fatalf("pagination = token %q has_more %v", env.Data.PageToken, env.Data.HasMore)
}
if len(env.Data.Items) != 1 {
t.Fatalf("items len = %d", len(env.Data.Items))
}
item := env.Data.Items[0]
if item["level"] != "ERROR" || item["severity_text"] != "ERROR" || item["severityText"] != "ERROR" {
t.Fatalf("level fields = %#v", item)
}
}
func TestAppsLogList_NormalizesKVAttributesToObject(t *testing.T) {
factory, stdout, reg := newAppsExecuteFactory(t)
reg.Register(&httpmock.Stub{
Method: "POST",
URL: "/open-apis/spark/v1/apps/app_x/search_logs",
Body: map[string]interface{}{
"code": 0,
"data": map[string]interface{}{
"log_items": []interface{}{
map[string]interface{}{
"log_id": "LOG1",
"attributes": []interface{}{
map[string]interface{}{"key": "app_env", "value": "runtime"},
map[string]interface{}{"key": "duration_ms", "value": "8263"},
map[string]interface{}{"key": "module", "value": "gateway"},
},
},
},
},
},
})
if err := runAppsShortcut(t, AppsLogList, []string{"+log-list", "--app-id", "app_x", "--as", "user"}, factory, stdout); err != nil {
t.Fatalf("execute err=%v", err)
}
var env struct {
Data struct {
Items []map[string]interface{} `json:"items"`
} `json:"data"`
}
if err := json.Unmarshal(stdout.Bytes(), &env); err != nil {
t.Fatalf("decode output: %v\n%s", err, stdout.String())
}
attrs, ok := env.Data.Items[0]["attributes"].(map[string]interface{})
if !ok {
t.Fatalf("attributes = %#v, want object", env.Data.Items[0]["attributes"])
}
if attrs["app_env"] != "runtime" || attrs["duration_ms"] != "8263" || attrs["module"] != "gateway" {
t.Fatalf("attributes = %#v", attrs)
}
}
func TestAppsLogGet_PrettyFormatsTimestamp(t *testing.T) {
const rawNS = int64(1782209472123456789)
factory, stdout, reg := newAppsExecuteFactory(t)
reg.Register(&httpmock.Stub{
Method: "POST",
URL: "/open-apis/spark/v1/apps/app_x/search_logs",
Body: map[string]interface{}{
"code": 0,
"data": map[string]interface{}{
"log_items": []interface{}{
map[string]interface{}{
"log_id": "LOG1",
"level": "ERROR",
"trace_id": "trace-1",
"timestamp_ns": rawNS,
"message": "boom",
},
},
},
},
})
if err := runAppsShortcut(t, AppsLogGet, []string{
"+log-get", "--app-id", "app_x", "--log-id", "LOG1", "--format", "pretty", "--as", "user",
}, factory, stdout); err != nil {
t.Fatalf("execute err=%v", err)
}
got := stdout.String()
wantTime := time.Unix(0, rawNS).Local().Format("2006-01-02 15:04:05.000")
if !strings.HasPrefix(got, "time") {
t.Fatalf("pretty output should start with time column, got:\n%s", got)
}
if !strings.Contains(got, wantTime) {
t.Fatalf("pretty output missing formatted time %q:\n%s", wantTime, got)
}
if strings.Contains(got, "timestamp_ns") || strings.Contains(got, "1782209472123456789") {
t.Fatalf("pretty output should hide raw timestamp_ns, got:\n%s", got)
}
}
func TestAppsLogGet_ResolvesSourceStackWhenFieldsPresent(t *testing.T) {
factory, stdout, reg := newAppsExecuteFactory(t)
search := &httpmock.Stub{
Method: "POST",
URL: "/open-apis/spark/v1/apps/app_x/search_logs",
Body: map[string]interface{}{
"code": 0,
"data": map[string]interface{}{
"log_items": []interface{}{
map[string]interface{}{
"log_id": "LOG1",
"level": "ERROR",
"attributes": map[string]interface{}{
"commit_id": "commit_1",
"source_map_file_prefix": "sourcemaps/app",
"frames": []interface{}{
map[string]interface{}{"file": "main.js", "line": 10, "column": 20},
},
},
},
},
},
},
}
resolve := &httpmock.Stub{
Method: "POST",
URL: "/open-apis/spark/v1/apps/app_x/resolve_stack_trace",
Body: map[string]interface{}{
"code": 0,
"data": map[string]interface{}{
"source_stack": []interface{}{
map[string]interface{}{"file": "src/App.tsx", "line": 7, "column": 9},
},
},
},
}
reg.Register(search)
reg.Register(resolve)
if err := runAppsShortcut(t, AppsLogGet, []string{"+log-get", "--app-id", "app_x", "--log-id", "LOG1", "--as", "user"}, factory, stdout); err != nil {
t.Fatalf("execute err=%v", err)
}
var sent map[string]interface{}
if err := json.Unmarshal(resolve.CapturedBody, &sent); err != nil {
t.Fatal(err)
}
if sent["commit_id"] != "commit_1" || sent["source_map_file_prefix"] != "sourcemaps/app" {
t.Fatalf("resolve body missing source map fields: %#v", sent)
}
frames, ok := sent["frames"].([]interface{})
if !ok || len(frames) != 1 {
t.Fatalf("resolve frames = %#v", sent["frames"])
}
if got := stdout.String(); !strings.Contains(got, `"source_stack_status": "resolved"`) || !strings.Contains(got, "src/App.tsx") {
t.Fatalf("stdout missing resolved source stack: %s", got)
}
}
func TestAppsLogGet_ResolvesSourceStackFromNestedKVAttributes(t *testing.T) {
factory, stdout, reg := newAppsExecuteFactory(t)
search := &httpmock.Stub{
Method: "POST",
URL: "/open-apis/spark/v1/apps/app_x/search_logs",
Body: map[string]interface{}{
"code": 0,
"data": map[string]interface{}{
"log_items": []interface{}{
map[string]interface{}{
"log_id": "LOG7655249917057764881",
"severityText": "ERROR",
"attributes": []interface{}{
map[string]interface{}{"key": "commit_id", "value": "commit_nested"},
map[string]interface{}{"key": "source_map_file_prefix", "value": "sourcemaps/nested"},
map[string]interface{}{
"key": "exception",
"value": map[string]interface{}{
"stackTrace": strings.Join([]string{
"TypeError: failed to render",
" at render (https://cdn.example.com/assets/main.js:12:34)",
" at https://cdn.example.com/assets/chunk.js:56:78",
}, "\n"),
},
},
},
},
},
},
},
}
resolve := &httpmock.Stub{
Method: "POST",
URL: "/open-apis/spark/v1/apps/app_x/resolve_stack_trace",
Body: map[string]interface{}{
"code": 0,
"data": map[string]interface{}{
"source_stack": []interface{}{
map[string]interface{}{"file": "src/App.tsx", "line": 12, "column": 34},
},
},
},
}
reg.Register(search)
reg.Register(resolve)
if err := runAppsShortcut(t, AppsLogGet, []string{"+log-get", "--app-id", "app_x", "--log-id", "LOG7655249917057764881", "--as", "user"}, factory, stdout); err != nil {
t.Fatalf("execute err=%v", err)
}
var sent map[string]interface{}
if err := json.Unmarshal(resolve.CapturedBody, &sent); err != nil {
t.Fatal(err)
}
if sent["commit_id"] != "commit_nested" || sent["source_map_file_prefix"] != "sourcemaps/nested" {
t.Fatalf("resolve body missing nested source map fields: %#v", sent)
}
frames, ok := sent["frames"].([]interface{})
if !ok || len(frames) != 2 {
t.Fatalf("resolve frames = %#v, want parsed stack frames", sent["frames"])
}
frame, ok := frames[0].(map[string]interface{})
if !ok {
t.Fatalf("parsed frame = %#v, want object", frames[0])
}
if frame["function"] != "render" || frame["file_name"] != "main.js" || frame["line"] != float64(12) || frame["column"] != float64(34) {
t.Fatalf("parsed frame = %#v", frame)
}
bare, ok := frames[1].(map[string]interface{})
if !ok {
t.Fatalf("bare frame = %#v, want object", frames[1])
}
if bare["file_name"] != "chunk.js" || bare["line"] != float64(56) || bare["column"] != float64(78) {
t.Fatalf("bare frame = %#v", bare)
}
if got := stdout.String(); !strings.Contains(got, `"source_stack_status": "resolved"`) || !strings.Contains(got, "src/App.tsx") {
t.Fatalf("stdout missing resolved source stack: %s", got)
}
}
func TestAppsLogGet_ResolvesSourceStackFromReleaseCommitJSONStack(t *testing.T) {
factory, stdout, reg := newAppsExecuteFactory(t)
search := &httpmock.Stub{
Method: "POST",
URL: "/open-apis/spark/v1/apps/app_x/search_logs",
Body: map[string]interface{}{
"code": 0,
"data": map[string]interface{}{
"log_items": []interface{}{
map[string]interface{}{
"log_id": "LOG7655249917057764881",
"severityText": "ERROR",
"attributes": map[string]interface{}{
"tenant_id": "110564",
"release_commit_id": "4b393e4e0ca9ca1a855ba4585bc6750a7db2266f",
"stack": `[{"fileName":"main.js","line":3348,"column":540585},` +
`{"fileName":"main.js","line":3107,"column":51935},` +
`{"fileName":"main.js","line":62,"column":12516}]`,
},
},
},
},
},
}
resolve := &httpmock.Stub{
Method: "POST",
URL: "/open-apis/spark/v1/apps/app_x/resolve_stack_trace",
Body: map[string]interface{}{
"code": 0,
"data": map[string]interface{}{
"source_stack": []interface{}{
map[string]interface{}{"file": "src/App.tsx", "line": 42, "column": 7},
},
},
},
}
reg.Register(search)
reg.Register(resolve)
if err := runAppsShortcut(t, AppsLogGet, []string{"+log-get", "--app-id", "app_x", "--log-id", "LOG7655249917057764881", "--as", "user"}, factory, stdout); err != nil {
t.Fatalf("execute err=%v", err)
}
var sent map[string]interface{}
if err := json.Unmarshal(resolve.CapturedBody, &sent); err != nil {
t.Fatal(err)
}
if sent["commit_id"] != "4b393e4e0ca9ca1a855ba4585bc6750a7db2266f" || sent["source_map_file_prefix"] != defaultSourceMapPrefix || sent["tenant_id"] != "110564" {
t.Fatalf("resolve body missing release source map fields: %#v", sent)
}
frames, ok := sent["frames"].([]interface{})
if !ok || len(frames) != 3 {
t.Fatalf("resolve frames = %#v, want all valid generated frames", sent["frames"])
}
first, ok := frames[0].(map[string]interface{})
if !ok {
t.Fatalf("first frame = %#v, want object", frames[0])
}
if first["file_name"] != "main.js" || first["line"] != float64(3348) || first["column"] != float64(540585) {
t.Fatalf("first frame = %#v", first)
}
if got := stdout.String(); !strings.Contains(got, `"source_stack_status": "resolved"`) || !strings.Contains(got, "src/App.tsx") {
t.Fatalf("stdout missing resolved source stack: %s", got)
}
}
func TestAppsLogGet_ResolvesSourceStackFromJSONBodyStack(t *testing.T) {
factory, stdout, reg := newAppsExecuteFactory(t)
search := &httpmock.Stub{
Method: "POST",
URL: "/open-apis/spark/v1/apps/app_x/search_logs",
Body: map[string]interface{}{
"code": 0,
"data": map[string]interface{}{
"log_items": []interface{}{
map[string]interface{}{
"log_id": "LOG_BODY_STACK",
"severityText": "ERROR",
"attributes": map[string]interface{}{
"release_commit_id": "commit_body",
},
"body": `{"error":{"stack":"AxiosError: failed\n at request (https://cdn.example.com/client/assets/body.js:9:88)"}}`,
},
},
},
},
}
resolve := &httpmock.Stub{
Method: "POST",
URL: "/open-apis/spark/v1/apps/app_x/resolve_stack_trace",
Body: map[string]interface{}{
"code": 0,
"data": map[string]interface{}{
"source_stack": []interface{}{
map[string]interface{}{"file": "src/request.ts", "line": 9, "column": 88},
},
},
},
}
reg.Register(search)
reg.Register(resolve)
if err := runAppsShortcut(t, AppsLogGet, []string{"+log-get", "--app-id", "app_x", "--log-id", "LOG_BODY_STACK", "--as", "user"}, factory, stdout); err != nil {
t.Fatalf("execute err=%v", err)
}
var sent map[string]interface{}
if err := json.Unmarshal(resolve.CapturedBody, &sent); err != nil {
t.Fatal(err)
}
if sent["commit_id"] != "commit_body" || sent["source_map_file_prefix"] != defaultSourceMapPrefix {
t.Fatalf("resolve body missing body stack source map fields: %#v", sent)
}
frames, ok := sent["frames"].([]interface{})
if !ok || len(frames) != 1 {
t.Fatalf("resolve frames = %#v, want parsed JSON body stack frame", sent["frames"])
}
frame, ok := frames[0].(map[string]interface{})
if !ok {
t.Fatalf("frame = %#v, want object", frames[0])
}
if frame["function"] != "request" || frame["file_name"] != "body.js" || frame["line"] != float64(9) || frame["column"] != float64(88) {
t.Fatalf("frame = %#v", frame)
}
if got := stdout.String(); !strings.Contains(got, `"source_stack_status": "resolved"`) || !strings.Contains(got, "src/request.ts") {
t.Fatalf("stdout missing resolved source stack: %s", got)
}
}
func TestAppsLogGet_SourceStackMissingFieldsDoesNotFail(t *testing.T) {
factory, stdout, reg := newAppsExecuteFactory(t)
search := &httpmock.Stub{
Method: "POST",
URL: "/open-apis/spark/v1/apps/app_x/search_logs",
Body: map[string]interface{}{
"code": 0,
"data": map[string]interface{}{
"log_items": []interface{}{
map[string]interface{}{
"log_id": "LOG1",
"level": "ERROR",
"message": "TypeError at https://cdn.example.com/main.js:10:20",
"attributes": map[string]interface{}{"commit_id": "commit_1"},
},
},
},
},
}
reg.Register(search)
if err := runAppsShortcut(t, AppsLogGet, []string{"+log-get", "--app-id", "app_x", "--log-id", "LOG1", "--as", "user"}, factory, stdout); err != nil {
t.Fatalf("execute err=%v", err)
}
if got := stdout.String(); !strings.Contains(got, `"log_id": "LOG1"`) {
t.Fatalf("stdout missing original log: %s", got)
} else if !strings.Contains(got, `"source_stack_status": "unresolved"`) {
t.Fatalf("stdout missing unresolved source stack status: %s", got)
} else if !strings.Contains(got, `"source_stack_reason"`) {
t.Fatalf("stdout missing sanitized source stack reason: %s", got)
}
for _, banned := range []string{"secret", "token", "raw request payload"} {
if strings.Contains(strings.ToLower(stdout.String()), banned) {
t.Fatalf("stdout leaked %q: %s", banned, stdout.String())
}
}
}
func TestAppsLogGet_ErrorNonFrontendMissingFieldsDoesNotMarkUnresolved(t *testing.T) {
factory, stdout, reg := newAppsExecuteFactory(t)
reg.Register(&httpmock.Stub{
Method: "POST",
URL: "/open-apis/spark/v1/apps/app_x/search_logs",
Body: map[string]interface{}{
"code": 0,
"data": map[string]interface{}{
"log_items": []interface{}{
map[string]interface{}{
"log_id": "LOG1",
"level": "ERROR",
"message": "go stack trace: database query failed",
},
},
},
},
})
if err := runAppsShortcut(t, AppsLogGet, []string{"+log-get", "--app-id", "app_x", "--log-id", "LOG1", "--as", "user"}, factory, stdout); err != nil {
t.Fatalf("execute err=%v", err)
}
if got := stdout.String(); strings.Contains(got, "source_stack_status") {
t.Fatalf("non-frontend error log should not be marked unresolved: %s", got)
}
}
func TestAppsLogGet_SourceStackResolveFailureIsRedacted(t *testing.T) {
factory, stdout, reg := newAppsExecuteFactory(t)
search := &httpmock.Stub{
Method: "POST",
URL: "/open-apis/spark/v1/apps/app_x/search_logs",
Body: map[string]interface{}{
"code": 0,
"data": map[string]interface{}{
"log_items": []interface{}{
map[string]interface{}{
"log_id": "LOG1",
"level": "ERROR",
"attributes": map[string]interface{}{
"commit_id": "commit_1",
"source_map_file_prefix": "sourcemaps/app",
"frames": []interface{}{
map[string]interface{}{"file": "main.js", "line": 10, "column": 20},
},
},
},
},
},
},
}
resolve := &httpmock.Stub{
Method: "POST",
URL: "/open-apis/spark/v1/apps/app_x/resolve_stack_trace",
Body: map[string]interface{}{
"code": 999,
"msg": "secret token raw request payload should be redacted",
},
}
reg.Register(search)
reg.Register(resolve)
if err := runAppsShortcut(t, AppsLogGet, []string{"+log-get", "--app-id", "app_x", "--log-id", "LOG1", "--as", "user"}, factory, stdout); err != nil {
t.Fatalf("execute err=%v", err)
}
got := stdout.String()
if !strings.Contains(got, `"source_stack_status": "unresolved"`) {
t.Fatalf("stdout missing unresolved status: %s", got)
}
if !strings.Contains(got, `"source_stack_error_code": 999`) {
t.Fatalf("stdout missing resolve error code: %s", got)
}
for _, banned := range []string{"secret", "token", "raw request payload"} {
if strings.Contains(strings.ToLower(got), banned) {
t.Fatalf("stdout leaked %q: %s", banned, got)
}
}
}