mirror of
https://github.com/larksuite/cli.git
synced 2026-07-07 17:20:00 +08:00
Add BuildResourceURL helper and wire it into doc/sheets/drive/base/wiki create paths so callers always receive a clickable link, even when the backend response (MCP degraded path or upstream OpenAPI) returns an empty URL field. The fallback uses the brand-standard host (www.feishu.cn / www.larksuite.com), which redirects to the tenant domain. Affected entries: - docs +create v1 / v2 - sheets +create - drive +create-folder / +import / +upload (newly exposes url) - wiki +node-create (newly exposes url) drive +create-shortcut is intentionally skipped because the URL form depends on the underlying file kind, which the shortcut payload does not carry.
649 lines
19 KiB
Go
649 lines
19 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package wiki
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/larksuite/cli/internal/cmdutil"
|
|
"github.com/larksuite/cli/internal/core"
|
|
"github.com/larksuite/cli/internal/httpmock"
|
|
"github.com/larksuite/cli/shortcuts/common"
|
|
)
|
|
|
|
type fakeWikiNodeCreateCall struct {
|
|
SpaceID string
|
|
Spec wikiNodeCreateSpec
|
|
}
|
|
|
|
type fakeWikiNodeCreateClient struct {
|
|
spaces map[string]*wikiSpaceRecord
|
|
nodes map[string]*wikiNodeRecord
|
|
createNode *wikiNodeRecord
|
|
returnNilNode bool
|
|
createErr error
|
|
getSpaceErr error
|
|
getNodeErr error
|
|
createInvoked []fakeWikiNodeCreateCall
|
|
}
|
|
|
|
func (fake *fakeWikiNodeCreateClient) GetNode(ctx context.Context, token string) (*wikiNodeRecord, error) {
|
|
if fake.getNodeErr != nil {
|
|
return nil, fake.getNodeErr
|
|
}
|
|
node, ok := fake.nodes[token]
|
|
if !ok {
|
|
return &wikiNodeRecord{}, nil
|
|
}
|
|
return node, nil
|
|
}
|
|
|
|
func (fake *fakeWikiNodeCreateClient) GetSpace(ctx context.Context, spaceID string) (*wikiSpaceRecord, error) {
|
|
if fake.getSpaceErr != nil {
|
|
return nil, fake.getSpaceErr
|
|
}
|
|
space, ok := fake.spaces[spaceID]
|
|
if !ok {
|
|
return &wikiSpaceRecord{}, nil
|
|
}
|
|
return space, nil
|
|
}
|
|
|
|
func (fake *fakeWikiNodeCreateClient) CreateNode(ctx context.Context, spaceID string, spec wikiNodeCreateSpec) (*wikiNodeRecord, error) {
|
|
fake.createInvoked = append(fake.createInvoked, fakeWikiNodeCreateCall{
|
|
SpaceID: spaceID,
|
|
Spec: spec,
|
|
})
|
|
if fake.createErr != nil {
|
|
return nil, fake.createErr
|
|
}
|
|
if fake.returnNilNode {
|
|
return nil, nil
|
|
}
|
|
if fake.createNode != nil {
|
|
return fake.createNode, nil
|
|
}
|
|
return &wikiNodeRecord{SpaceID: spaceID, Title: spec.Title, NodeType: spec.NodeType, ObjType: spec.ObjType}, nil
|
|
}
|
|
|
|
var wikiTestConfigSeq atomic.Int64
|
|
|
|
func wikiTestConfig() *core.CliConfig {
|
|
return &core.CliConfig{
|
|
AppID: fmt.Sprintf("wiki-test-app-%d", wikiTestConfigSeq.Add(1)),
|
|
AppSecret: "test-secret",
|
|
Brand: core.BrandFeishu,
|
|
}
|
|
}
|
|
|
|
func wikiPermissionTestConfig(userOpenID string) *core.CliConfig {
|
|
return &core.CliConfig{
|
|
AppID: fmt.Sprintf("wiki-permission-test-app-%d", wikiTestConfigSeq.Add(1)),
|
|
AppSecret: "test-secret",
|
|
Brand: core.BrandFeishu,
|
|
UserOpenId: userOpenID,
|
|
}
|
|
}
|
|
|
|
func mountAndRunWiki(t *testing.T, shortcut common.Shortcut, args []string, factory *cmdutil.Factory, stdout *bytes.Buffer) error {
|
|
t.Helper()
|
|
parent := &cobra.Command{Use: "wiki"}
|
|
shortcut.Mount(parent, factory)
|
|
parent.SetArgs(args)
|
|
parent.SilenceErrors = true
|
|
parent.SilenceUsage = true
|
|
if stdout != nil {
|
|
stdout.Reset()
|
|
}
|
|
return parent.Execute()
|
|
}
|
|
|
|
func TestWikiShortcutsIncludeAllCommands(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
shortcuts := Shortcuts()
|
|
if len(shortcuts) != 3 {
|
|
t.Fatalf("len(Shortcuts()) = %d, want 3", len(shortcuts))
|
|
}
|
|
if shortcuts[0].Command != "+move" {
|
|
t.Fatalf("shortcuts[0].Command = %q, want %q", shortcuts[0].Command, "+move")
|
|
}
|
|
if shortcuts[1].Command != "+node-create" {
|
|
t.Fatalf("shortcuts[1].Command = %q, want %q", shortcuts[1].Command, "+node-create")
|
|
}
|
|
if shortcuts[2].Command != "+delete-space" {
|
|
t.Fatalf("shortcuts[2].Command = %q, want %q", shortcuts[2].Command, "+delete-space")
|
|
}
|
|
}
|
|
|
|
func TestValidateWikiNodeCreateSpecRejectsShortcutWithoutOriginNodeToken(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
err := validateWikiNodeCreateSpec(wikiNodeCreateSpec{
|
|
NodeType: wikiNodeTypeShortcut,
|
|
ObjType: "docx",
|
|
}, core.AsUser)
|
|
if err == nil || !strings.Contains(err.Error(), "--origin-node-token is required") {
|
|
t.Fatalf("expected shortcut origin-token error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateWikiNodeCreateSpecRejectsOriginTokenForOriginNode(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
err := validateWikiNodeCreateSpec(wikiNodeCreateSpec{
|
|
NodeType: wikiNodeTypeOrigin,
|
|
ObjType: "docx",
|
|
OriginNodeToken: "wik_origin",
|
|
}, core.AsUser)
|
|
if err == nil || !strings.Contains(err.Error(), "can only be used when --node-type=shortcut") {
|
|
t.Fatalf("expected origin-node-token validation error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateWikiNodeCreateSpecRejectsBotWithoutLocation(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
err := validateWikiNodeCreateSpec(wikiNodeCreateSpec{
|
|
NodeType: wikiNodeTypeOrigin,
|
|
ObjType: "docx",
|
|
}, core.AsBot)
|
|
if err == nil || !strings.Contains(err.Error(), "bot identity requires --space-id or --parent-node-token") {
|
|
t.Fatalf("expected bot location validation error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateWikiNodeCreateSpecRejectsBotMyLibrarySpaceID(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
err := validateWikiNodeCreateSpec(wikiNodeCreateSpec{
|
|
NodeType: wikiNodeTypeOrigin,
|
|
ObjType: "docx",
|
|
SpaceID: wikiMyLibrarySpaceID,
|
|
ParentNodeToken: "wik_parent",
|
|
}, core.AsBot)
|
|
if err == nil || !strings.Contains(err.Error(), "bot identity does not support --space-id my_library") {
|
|
t.Fatalf("expected bot my_library validation error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestResolveWikiNodeCreateSpaceUsesParentNode(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client := &fakeWikiNodeCreateClient{
|
|
nodes: map[string]*wikiNodeRecord{
|
|
"wik_parent": {SpaceID: "space_parent"},
|
|
},
|
|
}
|
|
|
|
resolved, err := resolveWikiNodeCreateSpace(context.Background(), client, core.AsUser, wikiNodeCreateSpec{
|
|
NodeType: wikiNodeTypeOrigin,
|
|
ObjType: "docx",
|
|
ParentNodeToken: "wik_parent",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("resolveWikiNodeCreateSpace() error = %v", err)
|
|
}
|
|
if resolved.SpaceID != "space_parent" {
|
|
t.Fatalf("resolved space_id = %q, want %q", resolved.SpaceID, "space_parent")
|
|
}
|
|
if resolved.ResolvedBy != wikiResolvedByParentNode {
|
|
t.Fatalf("resolved_by = %q, want %q", resolved.ResolvedBy, wikiResolvedByParentNode)
|
|
}
|
|
}
|
|
|
|
func TestResolveWikiNodeCreateSpaceRejectsSpaceMismatch(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client := &fakeWikiNodeCreateClient{
|
|
nodes: map[string]*wikiNodeRecord{
|
|
"wik_parent": {SpaceID: "space_parent"},
|
|
},
|
|
}
|
|
|
|
_, err := resolveWikiNodeCreateSpace(context.Background(), client, core.AsUser, wikiNodeCreateSpec{
|
|
NodeType: wikiNodeTypeOrigin,
|
|
ObjType: "docx",
|
|
SpaceID: "space_other",
|
|
ParentNodeToken: "wik_parent",
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "does not match") {
|
|
t.Fatalf("expected mismatch error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestResolveWikiNodeCreateSpaceUsesMyLibraryFallback(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client := &fakeWikiNodeCreateClient{
|
|
spaces: map[string]*wikiSpaceRecord{
|
|
wikiMyLibrarySpaceID: {SpaceID: "space_my_library", SpaceType: "my_library"},
|
|
},
|
|
}
|
|
|
|
resolved, err := resolveWikiNodeCreateSpace(context.Background(), client, core.AsUser, wikiNodeCreateSpec{
|
|
NodeType: wikiNodeTypeOrigin,
|
|
ObjType: "docx",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("resolveWikiNodeCreateSpace() error = %v", err)
|
|
}
|
|
if resolved.SpaceID != "space_my_library" {
|
|
t.Fatalf("resolved space_id = %q, want %q", resolved.SpaceID, "space_my_library")
|
|
}
|
|
if resolved.ResolvedBy != wikiResolvedByMyLibrary {
|
|
t.Fatalf("resolved_by = %q, want %q", resolved.ResolvedBy, wikiResolvedByMyLibrary)
|
|
}
|
|
}
|
|
|
|
func TestRunWikiNodeCreateCreatesNodeInResolvedSpace(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client := &fakeWikiNodeCreateClient{
|
|
spaces: map[string]*wikiSpaceRecord{
|
|
wikiMyLibrarySpaceID: {SpaceID: "space_my_library"},
|
|
},
|
|
createNode: &wikiNodeRecord{
|
|
SpaceID: "space_my_library",
|
|
NodeToken: "wik_created",
|
|
NodeType: wikiNodeTypeOrigin,
|
|
ObjType: "docx",
|
|
Title: "Roadmap",
|
|
},
|
|
}
|
|
|
|
spec := wikiNodeCreateSpec{
|
|
NodeType: wikiNodeTypeOrigin,
|
|
ObjType: "docx",
|
|
Title: "Roadmap",
|
|
}
|
|
execution, err := runWikiNodeCreate(context.Background(), client, core.AsUser, spec)
|
|
if err != nil {
|
|
t.Fatalf("runWikiNodeCreate() error = %v", err)
|
|
}
|
|
if len(client.createInvoked) != 1 {
|
|
t.Fatalf("create invoked %d times, want 1", len(client.createInvoked))
|
|
}
|
|
if client.createInvoked[0].SpaceID != "space_my_library" {
|
|
t.Fatalf("create space_id = %q, want %q", client.createInvoked[0].SpaceID, "space_my_library")
|
|
}
|
|
if execution.Node.NodeToken != "wik_created" {
|
|
t.Fatalf("created node token = %q, want %q", execution.Node.NodeToken, "wik_created")
|
|
}
|
|
if execution.ResolvedSpace.ResolvedBy != wikiResolvedByMyLibrary {
|
|
t.Fatalf("resolved_by = %q, want %q", execution.ResolvedSpace.ResolvedBy, wikiResolvedByMyLibrary)
|
|
}
|
|
}
|
|
|
|
func TestRunWikiNodeCreateRejectsNilCreatedNode(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client := &fakeWikiNodeCreateClient{
|
|
spaces: map[string]*wikiSpaceRecord{
|
|
wikiMyLibrarySpaceID: {SpaceID: "space_my_library", SpaceType: "my_library"},
|
|
},
|
|
returnNilNode: true,
|
|
}
|
|
|
|
_, err := runWikiNodeCreate(context.Background(), client, core.AsUser, wikiNodeCreateSpec{
|
|
NodeType: wikiNodeTypeOrigin,
|
|
ObjType: "docx",
|
|
Title: "Roadmap",
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "wiki node create returned no node") {
|
|
t.Fatalf("expected missing node error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWikiNodeCreateDryRunShowsMyLibraryLookup(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got := wikiNodeCreateDryRunAPIsForTest(t, func(cmd *cobra.Command) {
|
|
if err := cmd.Flags().Set("title", "My Node"); err != nil {
|
|
t.Fatalf("set --title: %v", err)
|
|
}
|
|
})
|
|
|
|
if len(got) != 2 {
|
|
t.Fatalf("len(dryRun.api) = %d, want 2", len(got))
|
|
}
|
|
if got[0].URL != "/open-apis/wiki/v2/spaces/my_library" {
|
|
t.Fatalf("first dry-run URL = %q, want my_library lookup", got[0].URL)
|
|
}
|
|
if got[1].URL != "/open-apis/wiki/v2/spaces/<resolved_space_id>/nodes" {
|
|
t.Fatalf("second dry-run URL = %q, want placeholder create URL", got[1].URL)
|
|
}
|
|
if got[1].Body["title"] != "My Node" {
|
|
t.Fatalf("dry-run create body = %#v", got[1].Body)
|
|
}
|
|
}
|
|
|
|
func TestWikiNodeCreateDryRunUsesParentNodeWithoutMyLibraryLookup(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got := wikiNodeCreateDryRunAPIsForTest(t, func(cmd *cobra.Command) {
|
|
if err := cmd.Flags().Set("title", "Child Node"); err != nil {
|
|
t.Fatalf("set --title: %v", err)
|
|
}
|
|
if err := cmd.Flags().Set("parent-node-token", "wik_parent"); err != nil {
|
|
t.Fatalf("set --parent-node-token: %v", err)
|
|
}
|
|
})
|
|
|
|
if len(got) != 2 {
|
|
t.Fatalf("len(dryRun.api) = %d, want 2", len(got))
|
|
}
|
|
if got[0].URL != "/open-apis/wiki/v2/spaces/get_node" {
|
|
t.Fatalf("first dry-run URL = %q, want parent node lookup", got[0].URL)
|
|
}
|
|
if got[1].URL != "/open-apis/wiki/v2/spaces/<resolved_space_id>/nodes" {
|
|
t.Fatalf("second dry-run URL = %q, want placeholder create URL", got[1].URL)
|
|
}
|
|
}
|
|
|
|
func TestWikiNodeCreateDryRunKeepsExplicitSpaceIDWhenParentProvided(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got := wikiNodeCreateDryRunAPIsForTest(t, func(cmd *cobra.Command) {
|
|
if err := cmd.Flags().Set("title", "Child Node"); err != nil {
|
|
t.Fatalf("set --title: %v", err)
|
|
}
|
|
if err := cmd.Flags().Set("space-id", "space_123"); err != nil {
|
|
t.Fatalf("set --space-id: %v", err)
|
|
}
|
|
if err := cmd.Flags().Set("parent-node-token", "wik_parent"); err != nil {
|
|
t.Fatalf("set --parent-node-token: %v", err)
|
|
}
|
|
})
|
|
|
|
if len(got) != 2 {
|
|
t.Fatalf("len(dryRun.api) = %d, want 2", len(got))
|
|
}
|
|
if got[0].URL != "/open-apis/wiki/v2/spaces/get_node" {
|
|
t.Fatalf("first dry-run URL = %q, want parent node lookup", got[0].URL)
|
|
}
|
|
if got[1].URL != "/open-apis/wiki/v2/spaces/space_123/nodes" {
|
|
t.Fatalf("second dry-run URL = %q, want explicit space create URL", got[1].URL)
|
|
}
|
|
}
|
|
|
|
func TestWikiNodeCreateDryRunShowsMyLibraryLookupWhenExplicitAndParentProvided(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got := wikiNodeCreateDryRunAPIsForTest(t, func(cmd *cobra.Command) {
|
|
if err := cmd.Flags().Set("title", "Child Node"); err != nil {
|
|
t.Fatalf("set --title: %v", err)
|
|
}
|
|
if err := cmd.Flags().Set("space-id", wikiMyLibrarySpaceID); err != nil {
|
|
t.Fatalf("set --space-id: %v", err)
|
|
}
|
|
if err := cmd.Flags().Set("parent-node-token", "wik_parent"); err != nil {
|
|
t.Fatalf("set --parent-node-token: %v", err)
|
|
}
|
|
})
|
|
|
|
if len(got) != 3 {
|
|
t.Fatalf("len(dryRun.api) = %d, want 3", len(got))
|
|
}
|
|
if got[0].URL != "/open-apis/wiki/v2/spaces/my_library" {
|
|
t.Fatalf("first dry-run URL = %q, want my_library lookup", got[0].URL)
|
|
}
|
|
if got[1].URL != "/open-apis/wiki/v2/spaces/get_node" {
|
|
t.Fatalf("second dry-run URL = %q, want parent node lookup", got[1].URL)
|
|
}
|
|
if got[2].URL != "/open-apis/wiki/v2/spaces/<resolved_space_id>/nodes" {
|
|
t.Fatalf("third dry-run URL = %q, want placeholder create URL", got[2].URL)
|
|
}
|
|
}
|
|
|
|
func wikiNodeCreateDryRunAPIsForTest(t *testing.T, setFlags func(*cobra.Command)) []struct {
|
|
Method string `json:"method"`
|
|
URL string `json:"url"`
|
|
Body map[string]interface{} `json:"body"`
|
|
} {
|
|
t.Helper()
|
|
|
|
cmd := &cobra.Command{Use: "wiki +node-create"}
|
|
cmd.Flags().String("space-id", "", "")
|
|
cmd.Flags().String("parent-node-token", "", "")
|
|
cmd.Flags().String("title", "", "")
|
|
cmd.Flags().String("node-type", wikiNodeTypeOrigin, "")
|
|
cmd.Flags().String("obj-type", "docx", "")
|
|
cmd.Flags().String("origin-node-token", "", "")
|
|
if setFlags != nil {
|
|
setFlags(cmd)
|
|
}
|
|
|
|
runtime := common.TestNewRuntimeContext(cmd, nil)
|
|
dry := WikiNodeCreate.DryRun(context.Background(), runtime)
|
|
if dry == nil {
|
|
t.Fatal("DryRun returned nil")
|
|
}
|
|
|
|
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 json: %v", err)
|
|
}
|
|
|
|
return got.API
|
|
}
|
|
|
|
func TestWikiNodeCreateMountedExecuteWithExplicitSpaceID(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/space_123/nodes",
|
|
Body: map[string]interface{}{
|
|
"code": 0,
|
|
"data": map[string]interface{}{
|
|
"node": map[string]interface{}{
|
|
"space_id": "space_123",
|
|
"node_token": "wik_created",
|
|
"obj_token": "docx_created",
|
|
"obj_type": "docx",
|
|
"parent_node_token": "",
|
|
"node_type": "origin",
|
|
"origin_node_token": "",
|
|
"title": "Wiki Node",
|
|
"has_child": false,
|
|
},
|
|
},
|
|
"msg": "success",
|
|
},
|
|
}
|
|
reg.Register(createStub)
|
|
|
|
err := mountAndRunWiki(t, WikiNodeCreate, []string{
|
|
"+node-create",
|
|
"--space-id", "space_123",
|
|
"--title", "Wiki Node",
|
|
"--as", "bot",
|
|
}, factory, stdout)
|
|
if err != nil {
|
|
t.Fatalf("mountAndRunWiki() error = %v", err)
|
|
}
|
|
|
|
var envelope struct {
|
|
OK bool `json:"ok"`
|
|
Data map[string]interface{} `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(stdout.Bytes(), &envelope); err != nil {
|
|
t.Fatalf("unmarshal stdout: %v", err)
|
|
}
|
|
if !envelope.OK {
|
|
t.Fatalf("expected ok=true, got stdout=%s", stdout.String())
|
|
}
|
|
if envelope.Data["resolved_by"] != wikiResolvedByExplicitSpaceID {
|
|
t.Fatalf("resolved_by = %#v, want %q", envelope.Data["resolved_by"], wikiResolvedByExplicitSpaceID)
|
|
}
|
|
if envelope.Data["node_token"] != "wik_created" {
|
|
t.Fatalf("node_token = %#v, want %q", envelope.Data["node_token"], "wik_created")
|
|
}
|
|
if got, want := envelope.Data["url"], "https://www.feishu.cn/wiki/wik_created"; got != want {
|
|
t.Fatalf("url = %#v, want %q", got, want)
|
|
}
|
|
|
|
var captured map[string]interface{}
|
|
if err := json.Unmarshal(createStub.CapturedBody, &captured); err != nil {
|
|
t.Fatalf("unmarshal captured request body: %v", err)
|
|
}
|
|
if captured["node_type"] != wikiNodeTypeOrigin {
|
|
t.Fatalf("captured node_type = %#v, want %q", captured["node_type"], wikiNodeTypeOrigin)
|
|
}
|
|
if captured["obj_type"] != "docx" {
|
|
t.Fatalf("captured obj_type = %#v, want %q", captured["obj_type"], "docx")
|
|
}
|
|
if captured["title"] != "Wiki Node" {
|
|
t.Fatalf("captured title = %#v, want %q", captured["title"], "Wiki Node")
|
|
}
|
|
if got := stderr.String(); !strings.Contains(got, "Created wiki node in space space_123 via explicit_space_id.") {
|
|
t.Fatalf("stderr = %q, want completed creation message", got)
|
|
}
|
|
}
|
|
|
|
func TestWikiNodeCreateBotAutoGrantSuccess(t *testing.T) {
|
|
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
|
|
|
factory, stdout, _, reg := cmdutil.TestFactory(t, wikiPermissionTestConfig("ou_current_user"))
|
|
|
|
createStub := &httpmock.Stub{
|
|
Method: "POST",
|
|
URL: "/open-apis/wiki/v2/spaces/space_123/nodes",
|
|
Body: map[string]interface{}{
|
|
"code": 0,
|
|
"data": map[string]interface{}{
|
|
"node": map[string]interface{}{
|
|
"space_id": "space_123",
|
|
"node_token": "wik_created",
|
|
"obj_token": "docx_created",
|
|
"obj_type": "docx",
|
|
"node_type": "origin",
|
|
"title": "Wiki Node",
|
|
"has_child": false,
|
|
},
|
|
},
|
|
"msg": "success",
|
|
},
|
|
}
|
|
reg.Register(createStub)
|
|
|
|
permStub := &httpmock.Stub{
|
|
Method: "POST",
|
|
URL: "/open-apis/drive/v1/permissions/wik_created/members",
|
|
Body: map[string]interface{}{
|
|
"code": 0,
|
|
"msg": "ok",
|
|
},
|
|
}
|
|
reg.Register(permStub)
|
|
|
|
err := mountAndRunWiki(t, WikiNodeCreate, []string{
|
|
"+node-create",
|
|
"--space-id", "space_123",
|
|
"--title", "Wiki Node",
|
|
"--as", "bot",
|
|
}, factory, stdout)
|
|
if err != nil {
|
|
t.Fatalf("mountAndRunWiki() error = %v", err)
|
|
}
|
|
|
|
data := decodeWikiEnvelope(t, stdout)
|
|
grant, _ := data["permission_grant"].(map[string]interface{})
|
|
if grant["status"] != common.PermissionGrantGranted {
|
|
t.Fatalf("permission_grant.status = %#v, want %q", grant["status"], common.PermissionGrantGranted)
|
|
}
|
|
if grant["user_open_id"] != "ou_current_user" {
|
|
t.Fatalf("permission_grant.user_open_id = %#v, want %q", grant["user_open_id"], "ou_current_user")
|
|
}
|
|
if grant["message"] != "Granted the current CLI user full_access (可管理权限) on the new wiki node." {
|
|
t.Fatalf("permission_grant.message = %#v", grant["message"])
|
|
}
|
|
|
|
var body map[string]interface{}
|
|
if err := json.Unmarshal(permStub.CapturedBody, &body); err != nil {
|
|
t.Fatalf("unmarshal permission body: %v", err)
|
|
}
|
|
if body["member_type"] != "openid" || body["member_id"] != "ou_current_user" || body["perm"] != "full_access" || body["type"] != "user" {
|
|
t.Fatalf("unexpected permission request body: %#v", body)
|
|
}
|
|
if body["perm_type"] != "container" {
|
|
t.Fatalf("perm_type = %#v, want %q", body["perm_type"], "container")
|
|
}
|
|
}
|
|
|
|
func TestWikiNodeCreateUserSkipsPermissionGrantAugmentation(t *testing.T) {
|
|
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
|
|
|
factory, stdout, _, reg := cmdutil.TestFactory(t, wikiPermissionTestConfig("ou_current_user"))
|
|
|
|
reg.Register(&httpmock.Stub{
|
|
Method: "POST",
|
|
URL: "/open-apis/wiki/v2/spaces/space_123/nodes",
|
|
Body: map[string]interface{}{
|
|
"code": 0,
|
|
"data": map[string]interface{}{
|
|
"node": map[string]interface{}{
|
|
"space_id": "space_123",
|
|
"node_token": "wik_created",
|
|
"obj_token": "docx_created",
|
|
"obj_type": "docx",
|
|
"node_type": "origin",
|
|
"title": "Wiki Node",
|
|
"has_child": false,
|
|
},
|
|
},
|
|
"msg": "success",
|
|
},
|
|
})
|
|
|
|
err := mountAndRunWiki(t, WikiNodeCreate, []string{
|
|
"+node-create",
|
|
"--space-id", "space_123",
|
|
"--title", "Wiki Node",
|
|
"--as", "user",
|
|
}, factory, stdout)
|
|
if err != nil {
|
|
t.Fatalf("mountAndRunWiki() error = %v", err)
|
|
}
|
|
|
|
data := decodeWikiEnvelope(t, stdout)
|
|
if _, ok := data["permission_grant"]; ok {
|
|
t.Fatalf("did not expect permission_grant in user mode output: %#v", data)
|
|
}
|
|
}
|
|
|
|
func TestAugmentWikiNodeCreateOutputReturnsEmptyMapForNilInput(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
if got := augmentWikiNodeCreateOutput(nil, nil); len(got) != 0 {
|
|
t.Fatalf("augmentWikiNodeCreateOutput(nil, nil) = %#v, want empty map", got)
|
|
}
|
|
|
|
if got := augmentWikiNodeCreateOutput(nil, &wikiNodeCreateExecution{}); len(got) != 0 {
|
|
t.Fatalf("augmentWikiNodeCreateOutput(nil, empty execution) = %#v, want empty map", got)
|
|
}
|
|
}
|