mirror of
https://github.com/larksuite/cli.git
synced 2026-08-03 08:32:46 +08:00
* fix: unify dry-run output contract
* fix: address dry-run review feedback
* fix(dryrun): tighten preview contract and unify data shape
- transcribe HTTP method verbatim in previews (HEAD/OPTIONS were
reported as GET); reject an empty method in api with a typed error
- unify the dry-run data payload across api/service/shortcut paths:
{api, context?: {app_id, user_open_id}}; drop data.as — the envelope
top-level identity is the single identity source
- mark pretty dry-run stdout with '# dry-run: request not sent' so logs
that drop stderr still show it was a preview
- extract the shared preview builder, collapse PrintDryRunWithFile's
loose params into FileUploadMeta, and fail loudly on nil previews
- revert description-marker identity parsing: stale prose must not
override corrected accessTokens (blocks legal user calls on
images.create); identity gating keys off accessTokens only
- pin the new contracts with tests: verbatim method, three-way context
parity, nil-preview error, empty-context omission, marker line
* docs(agents): add typed-data, faithful-transcription, and contract-test conventions
- typed struct at the boundary over map[string]interface{} threading;
distinct types where values could swap silently (internal/meta.Token)
- transcribe input verbatim in previews/transformations; reject
unhonorable flag combinations with typed errors instead of silently
substituting behavior
- contract tests must fail when the implementation is reverted
* test: migrate dry-run tests grown on main to the envelope format
main gained raw-format dry-run readers while the PR was in flight
(wiki drive export #1802, drive list comments #1845, slash commands,
sheets history, docs fetch, mail draft-send/triage, vc meeting events).
Migrate them to the envelope accessors (clie2e.DryRunGet / data-wrapped
decoders) and drop the now-redundant DryRunData extractions in files
unified on DryRunGet.
---------
Co-authored-by: guokexin.02 <264159873+Tantanz20020918@users.noreply.github.com>
84 lines
3.2 KiB
Go
84 lines
3.2 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||
// SPDX-License-Identifier: MIT
|
||
|
||
package apps
|
||
|
||
import (
|
||
"encoding/json"
|
||
"errors"
|
||
"strings"
|
||
"testing"
|
||
|
||
"github.com/larksuite/cli/errs"
|
||
"github.com/larksuite/cli/internal/httpmock"
|
||
)
|
||
|
||
const fileGetURL = "/open-apis/spark/v1/apps/app_x/storage/file"
|
||
|
||
// TestAppsFileGet_RequiresAppIDAndPath 验证空白 --app-id 与空白 --path 分别触发对应的 typed 校验错误。
|
||
func TestAppsFileGet_RequiresAppIDAndPath(t *testing.T) {
|
||
factory, stdout, _ := newAppsExecuteFactory(t)
|
||
err := runAppsShortcut(t, AppsFileGet,
|
||
[]string{"+file-get", "--app-id", " ", "--path", "/x.png", "--as", "user"}, factory, stdout)
|
||
var ve *errs.ValidationError
|
||
if !errors.As(err, &ve) {
|
||
t.Fatalf("err = %T %v, want *errs.ValidationError", err, err)
|
||
}
|
||
if ve.Param != "--app-id" {
|
||
t.Fatalf("Param = %q, want --app-id", ve.Param)
|
||
}
|
||
factory2, stdout2, _ := newAppsExecuteFactory(t)
|
||
err2 := runAppsShortcut(t, AppsFileGet,
|
||
[]string{"+file-get", "--app-id", "app_x", "--path", " ", "--as", "user"}, factory2, stdout2)
|
||
var ve2 *errs.ValidationError
|
||
if !errors.As(err2, &ve2) {
|
||
t.Fatalf("err = %T %v, want *errs.ValidationError", err2, err2)
|
||
}
|
||
if ve2.Param != "--path" {
|
||
t.Fatalf("Param = %q, want --path", ve2.Param)
|
||
}
|
||
}
|
||
|
||
// TestAppsFileGet_DryRunSendsPathQuery 验证 dry-run 输出 GET file,path 作为 query 参数下发。
|
||
func TestAppsFileGet_DryRunSendsPathQuery(t *testing.T) {
|
||
factory, stdout, _ := newAppsExecuteFactory(t)
|
||
if err := runAppsShortcut(t, AppsFileGet,
|
||
[]string{"+file-get", "--app-id", "app_x", "--path", "/x.png", "--dry-run", "--as", "user"}, factory, stdout); err != nil {
|
||
t.Fatalf("dry-run err=%v", err)
|
||
}
|
||
var env dryRunAPIEnvelope
|
||
_ = json.Unmarshal([]byte(stdout.String()), &env)
|
||
if env.API[0].Method != "GET" || env.API[0].URL != fileGetURL || env.API[0].Params["path"] != "/x.png" {
|
||
t.Fatalf("dry-run = %s %s params=%v", env.API[0].Method, env.API[0].URL, env.API[0].Params)
|
||
}
|
||
}
|
||
|
||
// TestAppsFileGet_SuccessAndPrettyKeyValue 验证 pretty key/value 展示 size 含 bytes、uploaded_by 只显示 name 且不泄漏 user id。
|
||
func TestAppsFileGet_SuccessAndPrettyKeyValue(t *testing.T) {
|
||
factory, stdout, reg := newAppsExecuteFactory(t)
|
||
reg.Register(&httpmock.Stub{
|
||
Method: "GET", URL: fileGetURL,
|
||
Body: map[string]interface{}{"code": 0, "data": map[string]interface{}{
|
||
"file_name": "logo.png", "path": "/1858537546760216.png",
|
||
"size_bytes": 24580, "type": "image/png",
|
||
"created_at": "2026-04-15T10:30:00Z",
|
||
"created_by": `{"id":"7311","name":"alice"}`,
|
||
}},
|
||
})
|
||
if err := runAppsShortcut(t, AppsFileGet,
|
||
[]string{"+file-get", "--app-id", "app_x", "--path", "/1858537546760216.png", "--format", "pretty", "--as", "user"}, factory, stdout); err != nil {
|
||
t.Fatalf("execute err=%v", err)
|
||
}
|
||
got := stdout.String()
|
||
// pretty key/value:size 含 bytes、uploaded_by 只展示 name。
|
||
for _, want := range []string{"file_name:", "24 KB (24580 bytes)", "uploaded_by: alice", "uploaded_at: 2026-04-15T10:30:00Z"} {
|
||
if !strings.Contains(got, want) {
|
||
t.Errorf("pretty missing %q:\n%s", want, got)
|
||
}
|
||
}
|
||
// pretty 不该泄漏 user id。
|
||
if strings.Contains(got, "7311") {
|
||
t.Errorf("pretty should show name only, not id:\n%s", got)
|
||
}
|
||
}
|