Files
dingtalk-workspace-cli/internal/helpers/file_comment_test.go

648 lines
23 KiB
Go

// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package helpers
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"reflect"
"strings"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contractfinal"
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/testseam"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition"
"github.com/spf13/cobra"
)
type fileCommentTestCall struct {
server string
tool string
args map[string]any
}
type fileCommentTestCaller struct {
calls []fileCommentTestCall
responses []string
err error
dryRun bool
}
func (c *fileCommentTestCaller) CallTool(_ context.Context, server, tool string, args map[string]any) (*edition.ToolResult, error) {
copied := make(map[string]any, len(args))
for key, value := range args {
copied[key] = value
}
c.calls = append(c.calls, fileCommentTestCall{server: server, tool: tool, args: copied})
if c.err != nil {
return nil, c.err
}
response := `{"result":{"fileId":"file-1","comment":{"commentId":"1","content":"ok","anchor":{"version":"v1","surface":"file","scope":"whole"}}}}`
if len(c.responses) > 0 {
response = c.responses[0]
c.responses = c.responses[1:]
}
return &edition.ToolResult{Content: []edition.ContentBlock{{Type: "text", Text: response}}}, nil
}
func (c *fileCommentTestCaller) Format() string { return "json" }
func (c *fileCommentTestCaller) DryRun() bool { return c.dryRun }
func (c *fileCommentTestCaller) Fields() string { return "" }
func (c *fileCommentTestCaller) JQ() string { return "" }
func executeFileCommentCommand(t *testing.T, caller *fileCommentTestCaller, stdin string, args ...string) ([]byte, error) {
t.Helper()
testseam.Protect(t, &deps)
InitDeps(caller)
var stdout bytes.Buffer
deps.Out.w = &stdout
deps.Out.errW = io.Discard
root := &cobra.Command{Use: "dws", SilenceErrors: true, SilenceUsage: true}
root.SetOut(&stdout)
root.SetErr(io.Discard)
root.SetIn(strings.NewReader(stdin))
root.PersistentFlags().Bool("yes", false, "")
root.PersistentFlags().Bool("dry-run", false, "")
root.PersistentFlags().String("format", "json", "")
drive := &cobra.Command{Use: "drive"}
drive.AddCommand(newDriveFileCommentCmd())
root.AddCommand(drive)
var setOutput func(*cobra.Command)
setOutput = func(command *cobra.Command) {
command.SetOut(&stdout)
command.SetErr(io.Discard)
for _, child := range command.Commands() {
setOutput(child)
}
}
setOutput(root)
root.SetArgs(args)
err := root.Execute()
return stdout.Bytes(), err
}
func decodeFileCommentTestOutput(t *testing.T, raw []byte) map[string]any {
t.Helper()
var payload map[string]any
if err := json.Unmarshal(raw, &payload); err != nil {
t.Fatalf("decode output %q: %v", raw, err)
}
return payload
}
func TestDriveFileCommentListMapsFiltersAndProjects(t *testing.T) {
caller := &fileCommentTestCaller{responses: []string{`{
"result": {
"fileId": "resolved-file",
"total": 2,
"count": 2,
"hasMore": false,
"nextToken": null,
"items": [
{
"commentId": "101",
"parentCommentId": null,
"content": "whole",
"creatorId": "user-1",
"creatorName": "Alice",
"creatorAvatar": "https://avatar/1",
"createdAt": 1785920000000,
"updatedAt": 1785920000100,
"commentCustomType": "common",
"anchor": {"version":"v1","surface":"file","scope":"whole"}
},
{
"commentId": "102",
"content": "partial",
"commentCustomType": "highlight",
"options": {"page":"1"},
"anchor": {"version":"v1","surface":"file","scope":"partial","selector":{"kind":"legacy-highlight"}}
}
]
}
}`}}
out, err := executeFileCommentCommand(t, caller, "",
"drive", "comment", "list",
"--node", "https://alidocs.dingtalk.com/i/drive/file",
"--space-id", "123", "--limit", "20", "--scope", "whole",
)
if err != nil {
t.Fatal(err)
}
if len(caller.calls) != 1 {
t.Fatalf("calls = %#v", caller.calls)
}
call := caller.calls[0]
if call.server != fileCommentServer || call.tool != listFileCommentsTool {
t.Fatalf("target = %s/%s", call.server, call.tool)
}
wantArgs := map[string]any{
"fileId": "https://alidocs.dingtalk.com/i/drive/file",
"spaceId": "123",
"maxResults": 20,
}
if !reflect.DeepEqual(call.args, wantArgs) {
t.Fatalf("args = %#v, want %#v", call.args, wantArgs)
}
payload := decodeFileCommentTestOutput(t, out)
if payload["nodeId"] != "resolved-file" || payload["total"] != float64(2) ||
payload["count"] != float64(1) || payload["complete"] != true || payload["scope"] != "whole" {
t.Fatalf("list output = %#v", payload)
}
if payload["nextCursor"] != nil || payload["hasMore"] != false {
t.Fatalf("pagination output = %#v", payload)
}
comments := payload["comments"].([]any)
comment := comments[0].(map[string]any)
if comment["commentId"] != "101" || comment["customType"] != "common" {
t.Fatalf("comment projection = %#v", comment)
}
creator := comment["creator"].(map[string]any)
if creator["userId"] != "user-1" || creator["name"] != "Alice" || creator["avatar"] != "https://avatar/1" {
t.Fatalf("creator projection = %#v", creator)
}
if _, leaked := comment["creatorId"]; leaked {
t.Fatalf("raw creator fields leaked: %#v", comment)
}
}
func TestDriveFileCommentListKeepsEmptyCommentsAsArray(t *testing.T) {
caller := &fileCommentTestCaller{responses: []string{`{
"fileId":"file-1","total":0,"count":0,"hasMore":false,"nextToken":null,"items":[]
}`}}
out, err := executeFileCommentCommand(t, caller, "",
"drive", "comment", "list", "--node", "file-1",
)
if err != nil {
t.Fatal(err)
}
payload := decodeFileCommentTestOutput(t, out)
comments, ok := payload["comments"].([]any)
if !ok || len(comments) != 0 {
t.Fatalf("comments = %#v, want an empty JSON array", payload["comments"])
}
if payload["total"] != float64(0) || payload["count"] != float64(0) ||
payload["hasMore"] != false || payload["complete"] != true || payload["nextCursor"] != nil {
t.Fatalf("empty list output = %#v", payload)
}
}
func TestDriveFileCommentListAllAggregatesPages(t *testing.T) {
caller := &fileCommentTestCaller{responses: []string{
`{"fileId":"file-1","total":3,"count":2,"hasMore":true,"nextToken":"2","items":[
{"commentId":"1","anchor":{"scope":"whole"}},
{"commentId":"2","anchor":{"scope":"partial"}}
]}`,
`{"fileId":"file-1","total":3,"count":1,"hasMore":false,"nextToken":null,"items":[
{"commentId":"3","anchor":{"scope":"partial"}}
]}`,
}}
out, err := executeFileCommentCommand(t, caller, "",
"drive", "comment", "list", "--node", "file-1", "--all", "--scope", "partial",
)
if err != nil {
t.Fatal(err)
}
if len(caller.calls) != 2 {
t.Fatalf("calls = %#v", caller.calls)
}
if caller.calls[0].args["maxResults"] != fileCommentMaxPageSize {
t.Fatalf("first args = %#v", caller.calls[0].args)
}
if _, ok := caller.calls[0].args["nextToken"]; ok {
t.Fatalf("first page unexpectedly has cursor: %#v", caller.calls[0].args)
}
if caller.calls[1].args["nextToken"] != "2" {
t.Fatalf("second args = %#v", caller.calls[1].args)
}
for _, call := range caller.calls {
if _, ok := call.args["all"]; ok {
t.Fatalf("local --all leaked to MCP: %#v", call.args)
}
if _, ok := call.args["scope"]; ok {
t.Fatalf("local --scope leaked to MCP: %#v", call.args)
}
}
payload := decodeFileCommentTestOutput(t, out)
if payload["total"] != float64(3) || payload["count"] != float64(2) ||
payload["hasMore"] != false || payload["complete"] != true || payload["nextCursor"] != nil {
t.Fatalf("aggregate output = %#v", payload)
}
comments := payload["comments"].([]any)
if comments[0].(map[string]any)["commentId"] != "2" || comments[1].(map[string]any)["commentId"] != "3" {
t.Fatalf("scope-filtered comments = %#v", comments)
}
}
func TestDriveFileCommentListRejectsPaginationAnomalies(t *testing.T) {
tests := []struct {
name string
args []string
response string
message string
}{
{
name: "missing cursor",
args: []string{"drive", "comment", "list", "--node", "file-1", "--all"},
response: `{"fileId":"file-1","total":1,"count":1,"hasMore":true,"nextToken":null,"items":[]}`,
message: "nextCursor 为空",
},
{
name: "stalled cursor",
args: []string{"drive", "comment", "list", "--node", "file-1", "--cursor", "2"},
response: `{"fileId":"file-1","total":2,"count":1,"hasMore":true,"nextToken":"2","items":[]}`,
message: "游标未前进",
},
{
name: "invalid server cursor",
args: []string{"drive", "comment", "list", "--node", "file-1"},
response: `{"fileId":"file-1","total":2,"count":1,"hasMore":true,"nextToken":"next","items":[]}`,
message: "不是非负数字游标",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
caller := &fileCommentTestCaller{responses: []string{tt.response}}
_, err := executeFileCommentCommand(t, caller, "", tt.args...)
var cliErr *CLIError
if !errors.As(err, &cliErr) || cliErr.Code != CodeContentTruncated || !strings.Contains(cliErr.Message, tt.message) {
t.Fatalf("error = %#v", err)
}
if len(caller.calls) != 1 {
t.Fatalf("calls = %#v", caller.calls)
}
})
}
}
func TestDriveFileCommentListValidatesLocalParameters(t *testing.T) {
tests := []struct {
name string
args []string
}{
{"all with cursor", []string{"--all", "--cursor", "1"}},
{"all with limit", []string{"--all", "--limit", "20"}},
{"zero limit", []string{"--limit", "0"}},
{"large limit", []string{"--page-size", "201"}},
{"nonnumeric cursor", []string{"--cursor", "next"}},
{"overflow cursor", []string{"--cursor", "999999999999999999999999999999"}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
caller := &fileCommentTestCaller{}
args := []string{"drive", "comment", "list", "--node", "file-1"}
args = append(args, tt.args...)
if _, err := executeFileCommentCommand(t, caller, "", args...); err == nil {
t.Fatal("invalid arguments unexpectedly succeeded")
}
if len(caller.calls) != 0 {
t.Fatalf("invalid arguments reached MCP: %#v", caller.calls)
}
})
}
}
func TestDriveFileCommentNumericNodeRequiresSpaceIDForListAndCreateAliases(t *testing.T) {
tests := []struct {
name string
args []string
}{
{
name: "list id alias",
args: []string{"drive", "comment", "list", "--id", "231773999335"},
},
{
name: "create file-id alias",
args: []string{"drive", "comment", "create", "--file-id", "231773999335", "--content", "test", "--yes"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
caller := &fileCommentTestCaller{}
_, err := executeFileCommentCommand(t, caller, "", tt.args...)
var cliErr *CLIError
if !errors.As(err, &cliErr) || cliErr.Code != CodeInvalidParam || !strings.Contains(cliErr.Message, "--space-id") {
t.Fatalf("error = %#v", err)
}
if len(caller.calls) != 0 {
t.Fatalf("numeric node without space-id reached MCP: %#v", caller.calls)
}
})
}
caller := &fileCommentTestCaller{responses: []string{`{
"fileId":"resolved-file","total":0,"count":0,"hasMore":false,"nextToken":null,"items":[]
}`}}
if _, err := executeFileCommentCommand(t, caller, "",
"drive", "comment", "list", "--url", "231773999335", "--space-id", "2402756201",
); err != nil {
t.Fatal(err)
}
if len(caller.calls) != 1 || caller.calls[0].args["fileId"] != "231773999335" || caller.calls[0].args["spaceId"] != "2402756201" {
t.Fatalf("numeric node with space-id args = %#v", caller.calls)
}
if allASCIIDigits("") {
t.Fatal("empty string unexpectedly accepted as numeric")
}
}
func TestDriveFileCommentCreateValidatesMapsAndProjects(t *testing.T) {
validContent := strings.Repeat("a", fileCommentMaxContentLength)
caller := &fileCommentTestCaller{responses: []string{`{"result":{"fileId":"resolved-file","comment":{
"commentId":"912345","parentCommentId":null,"content":"created","creatorId":"user-1",
"createdAt":1785920000000,"commentCustomType":"common",
"anchor":{"version":"v1","surface":"file","scope":"whole","selector":null}
}}}`}}
out, err := executeFileCommentCommand(t, caller, "",
"drive", "comment", "create", "--node", "file-1", "--space-id", "123",
"--content", validContent, "--yes",
)
if err != nil {
t.Fatal(err)
}
if len(caller.calls) != 1 || caller.calls[0].server != fileCommentServer || caller.calls[0].tool != createFileCommentTool {
t.Fatalf("calls = %#v", caller.calls)
}
wantArgs := map[string]any{"fileId": "file-1", "spaceId": "123", "content": validContent}
if !reflect.DeepEqual(caller.calls[0].args, wantArgs) {
t.Fatalf("args = %#v, want %#v", caller.calls[0].args, wantArgs)
}
payload := decodeFileCommentTestOutput(t, out)
if payload["nodeId"] != "resolved-file" || payload["commentId"] != "912345" ||
payload["content"] != "created" || payload["customType"] != "common" {
t.Fatalf("create output = %#v", payload)
}
if _, nested := payload["comment"]; nested {
t.Fatalf("create output was not flattened: %#v", payload)
}
for _, tc := range []struct {
name string
content string
}{
{"blank", " \t"},
{"ascii over limit", strings.Repeat("a", fileCommentMaxContentLength+1)},
{"utf16 over limit", strings.Repeat("😀", 1050)},
} {
t.Run(tc.name, func(t *testing.T) {
invalidCaller := &fileCommentTestCaller{}
_, err := executeFileCommentCommand(t, invalidCaller, "",
"drive", "comment", "create", "--node", "file-1", "--content", tc.content, "--yes",
)
if err == nil {
t.Fatal("invalid content unexpectedly succeeded")
}
if len(invalidCaller.calls) != 0 {
t.Fatalf("invalid content reached MCP: %#v", invalidCaller.calls)
}
})
}
}
func TestDriveFileCommentCreatePublishesAndEnforcesConfirmation(t *testing.T) {
group := newDriveFileCommentCmd()
create, remaining, err := group.Find([]string{"create"})
if err != nil || len(remaining) != 0 {
t.Fatalf("find create: remaining=%v err=%v", remaining, err)
}
final, ok := contractfinal.RuntimeContractFinal(create)
if !ok || final.Safety == nil {
t.Fatal("create command is missing ContractFinal Safety")
}
if safety := *final.Safety; safety.Effect != "write" || safety.Risk != "medium" ||
safety.Confirmation != "user_required" || safety.Idempotency != "unknown" {
t.Fatalf("create safety = %#v", safety)
}
caller := &fileCommentTestCaller{}
_, err = executeFileCommentCommand(t, caller, "",
"drive", "comment", "create", "--node", "file-1", "--content", "需要确认",
)
var appErr *apperrors.Error
if !errors.As(err, &appErr) || appErr.Reason != "confirmation_required" {
t.Fatalf("closed-stdin error = %#v", err)
}
if len(caller.calls) != 0 {
t.Fatalf("unconfirmed create reached MCP: %#v", caller.calls)
}
}
func TestDriveFileCommentRejectsMalformedResponseInsteadOfReturningEmpty(t *testing.T) {
caller := &fileCommentTestCaller{responses: []string{`{"result":{"fileId":"file-1","total":0,"hasMore":false}}`}}
_, err := executeFileCommentCommand(t, caller, "", "drive", "comment", "list", "--node", "file-1")
var cliErr *CLIError
if !errors.As(err, &cliErr) || cliErr.Code != CodeMCPToolError || !strings.Contains(cliErr.Message, "缺少 items") {
t.Fatalf("error = %#v", err)
}
}
func TestDriveFileCommentDryRunAndCallErrors(t *testing.T) {
for _, tt := range []struct {
name string
args []string
tool string
}{
{
name: "list dry run",
args: []string{"drive", "comment", "list", "--node", "file-1"},
tool: listFileCommentsTool,
},
{
name: "create dry run",
args: []string{"drive", "comment", "create", "--node", "file-1", "--content", "test", "--yes"},
tool: createFileCommentTool,
},
} {
t.Run(tt.name, func(t *testing.T) {
caller := &fileCommentTestCaller{dryRun: true}
out, err := executeFileCommentCommand(t, caller, "", tt.args...)
if err != nil {
t.Fatal(err)
}
if len(caller.calls) != 0 || !strings.Contains(string(out), `"tool": "`+tt.tool+`"`) {
t.Fatalf("calls = %#v, output = %s", caller.calls, out)
}
})
}
sentinel := errors.New("mcp unavailable")
for _, tt := range []struct {
name string
args []string
}{
{
name: "list call error",
args: []string{"drive", "comment", "list", "--node", "file-1", "--all"},
},
{
name: "create call error",
args: []string{"drive", "comment", "create", "--node", "file-1", "--content", "test", "--yes"},
},
} {
t.Run(tt.name, func(t *testing.T) {
caller := &fileCommentTestCaller{err: sentinel}
_, err := executeFileCommentCommand(t, caller, "", tt.args...)
if !errors.Is(err, sentinel) {
t.Fatalf("error = %#v", err)
}
})
}
}
func TestDriveFileCommentListResponseValidationBranches(t *testing.T) {
tests := []struct {
name string
response string
message string
}{
{name: "empty", response: "", message: "返回为空"},
{name: "invalid json", response: "{", message: "不是有效 JSON"},
{name: "missing file id", response: `{}`, message: "缺少 fileId"},
{name: "missing total", response: `{"fileId":"file-1","hasMore":false,"items":[]}`, message: "缺少 total"},
{name: "missing has more", response: `{"fileId":"file-1","total":0,"items":[]}`, message: "缺少布尔字段 hasMore"},
{name: "next token type", response: `{"fileId":"file-1","total":1,"hasMore":true,"nextToken":2,"items":[]}`, message: "nextToken 不是字符串"},
{name: "items type", response: `{"fileId":"file-1","total":0,"hasMore":false,"items":{}}`, message: "items 不是数组"},
{name: "item type", response: `{"fileId":"file-1","total":1,"hasMore":false,"items":[1]}`, message: "items[0] 不是对象"},
{name: "missing comment id", response: `{"fileId":"file-1","total":1,"hasMore":false,"items":[{"anchor":{}}]}`, message: "缺少 commentId"},
{name: "missing anchor", response: `{"fileId":"file-1","total":1,"hasMore":false,"items":[{"commentId":"1"}]}`, message: "缺少 anchor 对象"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
caller := &fileCommentTestCaller{responses: []string{tt.response}}
_, err := executeFileCommentCommand(t, caller, "", "drive", "comment", "list", "--node", "file-1")
var cliErr *CLIError
if !errors.As(err, &cliErr) || cliErr.Code != CodeMCPToolError || !strings.Contains(cliErr.Message, tt.message) {
t.Fatalf("error = %#v", err)
}
})
}
caller := &fileCommentTestCaller{responses: []string{`{
"data":{"fileId":"file-1","total":0,"hasMore":false,"nextToken":null,"items":null}
}`}}
out, err := executeFileCommentCommand(t, caller, "", "drive", "comment", "list", "--node", "file-1")
if err != nil {
t.Fatal(err)
}
if comments := decodeFileCommentTestOutput(t, out)["comments"].([]any); len(comments) != 0 {
t.Fatalf("comments = %#v", comments)
}
}
func TestDriveFileCommentCreateResponseValidationBranches(t *testing.T) {
tests := []struct {
name string
response string
message string
}{
{name: "invalid json", response: "{", message: "不是有效 JSON"},
{name: "missing file id", response: `{"comment":{"commentId":"1","anchor":{}}}`, message: "缺少 fileId"},
{name: "missing comment", response: `{"fileId":"file-1"}`, message: "缺少 comment 对象"},
{name: "missing comment id", response: `{"fileId":"file-1","comment":{"anchor":{}}}`, message: "缺少 commentId"},
{name: "missing anchor", response: `{"fileId":"file-1","comment":{"commentId":"1"}}`, message: "缺少 anchor 对象"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
caller := &fileCommentTestCaller{responses: []string{tt.response}}
_, err := executeFileCommentCommand(t, caller, "",
"drive", "comment", "create", "--node", "file-1", "--content", "test", "--yes",
)
var cliErr *CLIError
if !errors.As(err, &cliErr) || cliErr.Code != CodeMCPToolError || !strings.Contains(cliErr.Message, tt.message) {
t.Fatalf("error = %#v", err)
}
})
}
}
func TestDriveFileCommentPaginationCycleLimitAndPartialPage(t *testing.T) {
cycleCaller := &fileCommentTestCaller{responses: []string{
`{"fileId":"file-1","total":0,"hasMore":true,"nextToken":"2","items":[]}`,
`{"fileId":"file-1","total":0,"hasMore":true,"nextToken":"3","items":[]}`,
`{"fileId":"file-1","total":0,"hasMore":true,"nextToken":"2","items":[]}`,
}}
_, err := executeFileCommentCommand(t, cycleCaller, "", "drive", "comment", "list", "--node", "file-1", "--all")
var cliErr *CLIError
if !errors.As(err, &cliErr) || cliErr.Code != CodeContentTruncated || !strings.Contains(cliErr.Message, "发生循环") {
t.Fatalf("cycle error = %#v", err)
}
limitResponses := make([]string, 0, fileCommentMaxAutoPages)
for index := 0; index < fileCommentMaxAutoPages; index++ {
limitResponses = append(limitResponses, fmt.Sprintf(
`{"fileId":"file-1","total":0,"hasMore":true,"nextToken":"%d","items":[]}`,
index+1,
))
}
limitCaller := &fileCommentTestCaller{responses: limitResponses}
_, err = executeFileCommentCommand(t, limitCaller, "", "drive", "comment", "list", "--node", "file-1", "--all")
if !errors.As(err, &cliErr) || cliErr.Code != CodeContentTruncated || !strings.Contains(cliErr.Message, "10 页上限") {
t.Fatalf("page limit error = %#v", err)
}
partialCaller := &fileCommentTestCaller{responses: []string{`{
"fileId":"file-1","total":1,"hasMore":true,"nextToken":"2",
"items":[{"commentId":"1","anchor":{"scope":"whole"}}]
}`}}
out, err := executeFileCommentCommand(t, partialCaller, "", "drive", "comment", "list", "--node", "file-1")
if err != nil {
t.Fatal(err)
}
payload := decodeFileCommentTestOutput(t, out)
if payload["nextCursor"] != "2" || payload["complete"] != false || payload["hasMore"] != true {
t.Fatalf("partial page = %#v", payload)
}
overflowCaller := &fileCommentTestCaller{responses: []string{`{
"fileId":"file-1","total":0,"hasMore":true,
"nextToken":"999999999999999999999999999999","items":[]
}`}}
_, err = executeFileCommentCommand(t, overflowCaller, "", "drive", "comment", "list", "--node", "file-1")
if !errors.As(err, &cliErr) || cliErr.Code != CodeContentTruncated || !strings.Contains(cliErr.Message, "超出 64 位整数范围") {
t.Fatalf("overflow cursor error = %#v", err)
}
}
func TestDriveFileCommentInternalDefaults(t *testing.T) {
caller := &fileCommentTestCaller{responses: []string{`{
"fileId":"file-1","total":0,"hasMore":false,"nextToken":null,"items":[]
}`}}
testseam.Protect(t, &deps)
InitDeps(caller)
var stdout bytes.Buffer
cmd := &cobra.Command{Use: "list"}
cmd.SetOut(&stdout)
if err := runFileCommentList(cmd, listFileCommentsTool, map[string]any{"fileId": "file-1", "maxResults": 200}); err != nil {
t.Fatal(err)
}
payload := decodeFileCommentTestOutput(t, stdout.Bytes())
if payload["scope"] != "all" {
t.Fatalf("scope = %#v", payload["scope"])
}
if comments := fileCommentListPayload(fileCommentPage{}, "all")["comments"].([]map[string]any); len(comments) != 0 {
t.Fatalf("nil comments projection = %#v", comments)
}
}