mirror of
https://github.com/larksuite/cli.git
synced 2026-07-06 00:06:28 +08:00
- +node-get: wrap wiki.spaces.get_node; accepts node_token, obj_token, or a Lark URL (URL path auto-infers obj_type); formatted output with creator / updated_at. No synthesized url — get_node returns none and a BuildResourceURL fallback is a non-canonical link that misleads in a read/confirm command (sibling read shortcuts omit it too) - +node-delete: wrap space.node delete; high-risk-write (--yes gated), async delete-node task polling, auto-resolves space_id via get_node when --space-id omitted, actionable hints for codes 131011 / 131003. The delete-node task result lives under the gateway's generic `simple_task_result` key (NOT `delete_node_result`) - +space-create: wrap spaces.create; user-only identity, --name required (no empty-name spaces), flattened space output, no url - factor the shared wiki async-task poll loop into wiki_async_task.go; preserve upstream Lark Detail.Code on poll exhaustion (no longer rebuilt via lossy ErrWithHint) - drive +task_result: add wiki_delete_node scenario so +node-delete's async-timeout next_command actually resolves - skill docs: reference pages for the 3 new shortcuts + SKILL.md shortcuts table (no raw nodes.delete API exists — it's shortcut-only, so it is intentionally absent from API Resources / permission table); drop the circular TestWikiShortcutsIncludeAllCommands change-detector Change-Id: I316f78290cec5bc50f80d629173e3bf2a35dd005
208 lines
6.2 KiB
Go
208 lines
6.2 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package wiki
|
|
|
|
import (
|
|
"encoding/json"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/larksuite/cli/internal/cmdutil"
|
|
"github.com/larksuite/cli/internal/httpmock"
|
|
"github.com/larksuite/cli/shortcuts/common"
|
|
)
|
|
|
|
func TestWikiSpaceCreateDeclaredContract(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
if WikiSpaceCreate.Command != "+space-create" {
|
|
t.Fatalf("Command = %q, want +space-create", WikiSpaceCreate.Command)
|
|
}
|
|
if WikiSpaceCreate.Risk != "write" {
|
|
t.Fatalf("Risk = %q, want write", WikiSpaceCreate.Risk)
|
|
}
|
|
if !reflect.DeepEqual(WikiSpaceCreate.AuthTypes, []string{"user"}) {
|
|
t.Fatalf("AuthTypes = %v, want [user]", WikiSpaceCreate.AuthTypes)
|
|
}
|
|
if !reflect.DeepEqual(WikiSpaceCreate.Scopes, []string{"wiki:space:write_only"}) {
|
|
t.Fatalf("Scopes = %v, want [wiki:space:write_only]", WikiSpaceCreate.Scopes)
|
|
}
|
|
}
|
|
|
|
func TestReadWikiSpaceCreateSpecRejectsBlankName(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cmd := &cobra.Command{Use: "wiki +space-create"}
|
|
cmd.Flags().String("name", " ", "")
|
|
cmd.Flags().String("description", "", "")
|
|
|
|
runtime := common.TestNewRuntimeContext(cmd, nil)
|
|
if _, err := readWikiSpaceCreateSpec(runtime); err == nil || !strings.Contains(err.Error(), "--name is required") {
|
|
t.Fatalf("expected blank-name rejection, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWikiSpaceCreateRequestBody(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
nameOnly := wikiSpaceCreateSpec{Name: "Eng Wiki"}.RequestBody()
|
|
if !reflect.DeepEqual(nameOnly, map[string]interface{}{"name": "Eng Wiki"}) {
|
|
t.Fatalf("name-only body = %#v", nameOnly)
|
|
}
|
|
|
|
full := wikiSpaceCreateSpec{Name: "Eng Wiki", Description: "team docs"}.RequestBody()
|
|
if full["name"] != "Eng Wiki" || full["description"] != "team docs" {
|
|
t.Fatalf("full body = %#v", full)
|
|
}
|
|
}
|
|
|
|
func TestWikiSpaceCreateDryRun(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cmd := &cobra.Command{Use: "wiki +space-create"}
|
|
cmd.Flags().String("name", "Eng Wiki", "")
|
|
cmd.Flags().String("description", "team docs", "")
|
|
runtime := common.TestNewRuntimeContext(cmd, nil)
|
|
|
|
dry := WikiSpaceCreate.DryRun(nil, runtime)
|
|
data, err := json.Marshal(dry)
|
|
if err != nil {
|
|
t.Fatalf("marshal dry run: %v", err)
|
|
}
|
|
var got struct {
|
|
API []struct {
|
|
Method string `json:"method"`
|
|
URL string `json:"url"`
|
|
Body map[string]interface{} `json:"body"`
|
|
} `json:"api"`
|
|
}
|
|
if err := json.Unmarshal(data, &got); err != nil {
|
|
t.Fatalf("unmarshal dry run: %v", err)
|
|
}
|
|
if len(got.API) != 1 || got.API[0].Method != "POST" || got.API[0].URL != "/open-apis/wiki/v2/spaces" {
|
|
t.Fatalf("dry-run api = %#v", got.API)
|
|
}
|
|
if got.API[0].Body["name"] != "Eng Wiki" || got.API[0].Body["description"] != "team docs" {
|
|
t.Fatalf("dry-run body = %#v", got.API[0].Body)
|
|
}
|
|
}
|
|
|
|
func TestWikiSpaceCreateDryRunBlankNameSurfacesError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cmd := &cobra.Command{Use: "wiki +space-create"}
|
|
cmd.Flags().String("name", "", "")
|
|
cmd.Flags().String("description", "", "")
|
|
runtime := common.TestNewRuntimeContext(cmd, nil)
|
|
|
|
dry := WikiSpaceCreate.DryRun(nil, runtime)
|
|
data, _ := json.Marshal(dry)
|
|
if !strings.Contains(string(data), "--name is required") {
|
|
t.Fatalf("dry-run should surface validation error, got %s", data)
|
|
}
|
|
}
|
|
|
|
func TestWikiSpaceCreateMountedExecuteFlattensSpace(t *testing.T) {
|
|
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
|
|
|
factory, stdout, stderr, reg := cmdutil.TestFactory(t, wikiTestConfig())
|
|
|
|
createStub := &httpmock.Stub{
|
|
Method: "POST",
|
|
URL: "/open-apis/wiki/v2/spaces",
|
|
Body: map[string]interface{}{
|
|
"code": 0,
|
|
"data": map[string]interface{}{
|
|
"space": map[string]interface{}{
|
|
"space_id": "7160145948494381236",
|
|
"name": "Eng Wiki",
|
|
"description": "team docs",
|
|
"space_type": "team",
|
|
"visibility": "private",
|
|
"open_sharing": "closed",
|
|
},
|
|
},
|
|
"msg": "success",
|
|
},
|
|
}
|
|
reg.Register(createStub)
|
|
|
|
err := mountAndRunWiki(t, WikiSpaceCreate, []string{
|
|
"+space-create",
|
|
"--name", "Eng Wiki",
|
|
"--description", "team docs",
|
|
"--as", "user",
|
|
}, factory, stdout)
|
|
if err != nil {
|
|
t.Fatalf("mountAndRunWiki() error = %v", err)
|
|
}
|
|
|
|
data := decodeWikiEnvelope(t, stdout)
|
|
if data["space_id"] != "7160145948494381236" {
|
|
t.Fatalf("space_id = %#v", data["space_id"])
|
|
}
|
|
if data["name"] != "Eng Wiki" || data["description"] != "team docs" {
|
|
t.Fatalf("name/description = %#v / %#v", data["name"], data["description"])
|
|
}
|
|
if data["space_type"] != "team" || data["visibility"] != "private" || data["open_sharing"] != "closed" {
|
|
t.Fatalf("space_type/visibility/open_sharing = %#v", data)
|
|
}
|
|
if _, ok := data["url"]; ok {
|
|
t.Fatalf("output must not include a url field, got %#v", data["url"])
|
|
}
|
|
|
|
var captured map[string]interface{}
|
|
if err := json.Unmarshal(createStub.CapturedBody, &captured); err != nil {
|
|
t.Fatalf("unmarshal captured body: %v", err)
|
|
}
|
|
if captured["name"] != "Eng Wiki" || captured["description"] != "team docs" {
|
|
t.Fatalf("captured request body = %#v", captured)
|
|
}
|
|
if !strings.Contains(stderr.String(), "Created wiki space") {
|
|
t.Fatalf("stderr = %q, want creation log", stderr.String())
|
|
}
|
|
}
|
|
|
|
func TestWikiSpaceCreateRejectsBotIdentity(t *testing.T) {
|
|
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
|
|
|
factory, stdout, _, _ := cmdutil.TestFactory(t, wikiTestConfig())
|
|
|
|
err := mountAndRunWiki(t, WikiSpaceCreate, []string{
|
|
"+space-create",
|
|
"--name", "Eng Wiki",
|
|
"--as", "bot",
|
|
}, factory, stdout)
|
|
if err == nil || !strings.Contains(err.Error(), "only supports: user") {
|
|
t.Fatalf("expected bot identity rejection, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWikiSpaceCreateErrorsWhenNoSpaceReturned(t *testing.T) {
|
|
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
|
|
|
factory, stdout, _, reg := cmdutil.TestFactory(t, wikiTestConfig())
|
|
reg.Register(&httpmock.Stub{
|
|
Method: "POST",
|
|
URL: "/open-apis/wiki/v2/spaces",
|
|
Body: map[string]interface{}{
|
|
"code": 0,
|
|
"data": map[string]interface{}{},
|
|
"msg": "success",
|
|
},
|
|
})
|
|
|
|
err := mountAndRunWiki(t, WikiSpaceCreate, []string{
|
|
"+space-create",
|
|
"--name", "Eng Wiki",
|
|
"--as", "user",
|
|
}, factory, stdout)
|
|
if err == nil || !strings.Contains(err.Error(), "returned no space") {
|
|
t.Fatalf("expected missing-space error, got %v", err)
|
|
}
|
|
}
|