711 lines
34 KiB
Go
711 lines
34 KiB
Go
package helpers
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/testseam"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func runDocCoverageCommand(t *testing.T, caller edition.ToolCaller, args ...string) error {
|
|
t.Helper()
|
|
InitDeps(caller)
|
|
deps.Out.w = io.Discard
|
|
deps.Out.errW = io.Discard
|
|
root := newDocCommand()
|
|
installExampleGlobalFlags(root)
|
|
root.SilenceErrors = true
|
|
root.SilenceUsage = true
|
|
root.SetOut(io.Discard)
|
|
root.SetErr(io.Discard)
|
|
root.SetArgs(args)
|
|
return root.ExecuteContext(context.Background())
|
|
}
|
|
|
|
func TestCrossPlatformCoverageDocTransferAndDiffEdges(t *testing.T) {
|
|
oldArgs := os.Args
|
|
os.Args = []string{"dws", "doc"}
|
|
t.Cleanup(func() { os.Args = oldArgs })
|
|
caller := &scriptedToolCaller{steps: []scriptedToolStep{{text: `{"markdown":"before"}`}}}
|
|
installScriptedCaller(t, caller)
|
|
|
|
root := newDocCommand()
|
|
copyCmd, _, _ := root.Find([]string{"copy"})
|
|
_ = copyCmd.Flags().Set("node", "node")
|
|
_ = copyCmd.Flags().Set("folder", "12345")
|
|
if err := copyCmd.RunE(copyCmd, nil); err == nil {
|
|
t.Fatal("numeric document folder unexpectedly accepted")
|
|
}
|
|
|
|
previewCmd := root
|
|
var out bytes.Buffer
|
|
previewCmd.SetOut(&out)
|
|
if err := previewDocOverwriteDiff(context.Background(), previewCmd, "node", "after"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
caller.steps = []scriptedToolStep{{err: errors.New("read")}}
|
|
caller.index = 0
|
|
if err := previewDocOverwriteDiff(context.Background(), previewCmd, "node", "after"); err == nil {
|
|
t.Fatal("preview read failure returned nil")
|
|
}
|
|
if got := extractMarkdownField(`{"markdown":"body"}`); got != "body" {
|
|
t.Fatalf("markdown extraction = %q", got)
|
|
}
|
|
long := strings.TrimSuffix(strings.Repeat("line\n", 25), "\n")
|
|
diff := renderDocOverwriteDiff("node", long, long)
|
|
if !strings.Contains(diff, "5 more lines") {
|
|
t.Fatalf("long diff was not truncated: %s", diff)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageDocUploadAndMediaErrorEdges(t *testing.T) {
|
|
oldArgs := os.Args
|
|
os.Args = []string{"dws", "doc"}
|
|
t.Cleanup(func() { os.Args = oldArgs })
|
|
oldPut, oldGet := httpPutFile, httpGetFile
|
|
t.Cleanup(func() { httpPutFile, httpGetFile = oldPut, oldGet })
|
|
testseam.Swap(t, &helperSleep, func(time.Duration) {})
|
|
testseam.Swap(t, &docMediaVerifyWait, func(context.Context, time.Duration) error { return nil })
|
|
file := filepath.Join(t.TempDir(), "file.txt")
|
|
if err := os.WriteFile(file, []byte("content"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Run("upload input validation", func(t *testing.T) {
|
|
installScriptedCaller(t, &scriptedToolCaller{})
|
|
root := newDocCommand()
|
|
upload, _, _ := root.Find([]string{"upload"})
|
|
_ = upload.Flags().Set("file", t.TempDir())
|
|
if err := upload.RunE(upload, nil); err == nil {
|
|
t.Fatal("directory upload returned nil")
|
|
}
|
|
root = newDocCommand()
|
|
upload, _, _ = root.Find([]string{"upload"})
|
|
_ = upload.Flags().Set("file", filepath.Join(t.TempDir(), "missing"))
|
|
if err := upload.RunE(upload, nil); err == nil {
|
|
t.Fatal("missing upload returned nil")
|
|
}
|
|
root = newDocCommand()
|
|
upload, _, _ = root.Find([]string{"upload"})
|
|
_ = upload.Flags().Set("file", file)
|
|
_ = upload.Flags().Set("folder", "123")
|
|
if err := upload.RunE(upload, nil); err == nil {
|
|
t.Fatal("numeric folder returned nil")
|
|
}
|
|
})
|
|
|
|
t.Run("download failure", func(t *testing.T) {
|
|
installScriptedCaller(t, &scriptedToolCaller{steps: []scriptedToolStep{{text: `{"resourceUrl":"https://download/file"}`}}})
|
|
httpGetFile = func(context.Context, string, map[string]string, string) error { return errors.New("get") }
|
|
root := newDocCommand()
|
|
download, _, _ := root.Find([]string{"download"})
|
|
_ = download.Flags().Set("node", "node")
|
|
_ = download.Flags().Set("output", filepath.Join(t.TempDir(), "out"))
|
|
if err := download.RunE(download, nil); err == nil {
|
|
t.Fatal("download failure returned nil")
|
|
}
|
|
})
|
|
|
|
t.Run("upload put failure", func(t *testing.T) {
|
|
installScriptedCaller(t, &scriptedToolCaller{steps: []scriptedToolStep{{text: `{"resourceUrl":"https://upload","uploadKey":"key"}`}}})
|
|
httpPutFile = func(context.Context, string, map[string]string, string, int64) error { return errors.New("put") }
|
|
root := newDocCommand()
|
|
upload, _, _ := root.Find([]string{"upload"})
|
|
_ = upload.Flags().Set("file", file)
|
|
if err := upload.RunE(upload, nil); err == nil {
|
|
t.Fatal("put failure returned nil")
|
|
}
|
|
})
|
|
for _, tc := range []struct {
|
|
name string
|
|
step scriptedToolStep
|
|
}{
|
|
{"upload credential failure", scriptedToolStep{err: errors.New("credential")}},
|
|
{"upload credential parse failure", scriptedToolStep{text: `{}`}},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
installScriptedCaller(t, &scriptedToolCaller{steps: []scriptedToolStep{tc.step}})
|
|
root := newDocCommand()
|
|
upload, _, _ := root.Find([]string{"upload"})
|
|
_ = upload.Flags().Set("file", file)
|
|
if err := upload.RunE(upload, nil); err == nil {
|
|
t.Fatal("upload failure returned nil")
|
|
}
|
|
})
|
|
}
|
|
|
|
mediaCommand := func(t *testing.T, caller *scriptedToolCaller, path, mime string) error {
|
|
t.Helper()
|
|
installScriptedCaller(t, caller)
|
|
root := newDocCommand()
|
|
media, _, _ := root.Find([]string{"media", "insert"})
|
|
_ = media.Flags().Set("node", "node")
|
|
_ = media.Flags().Set("file", path)
|
|
if mime != "" {
|
|
_ = media.Flags().Set("mime-type", mime)
|
|
}
|
|
return media.RunE(media, nil)
|
|
}
|
|
|
|
t.Run("media directory", func(t *testing.T) {
|
|
if err := mediaCommand(t, &scriptedToolCaller{}, t.TempDir(), ""); err == nil {
|
|
t.Fatal("directory media returned nil")
|
|
}
|
|
})
|
|
t.Run("media missing file", func(t *testing.T) {
|
|
if err := mediaCommand(t, &scriptedToolCaller{}, filepath.Join(t.TempDir(), "missing"), ""); err == nil {
|
|
t.Fatal("missing media returned nil")
|
|
}
|
|
})
|
|
t.Run("media dry run", func(t *testing.T) {
|
|
if err := mediaCommand(t, &scriptedToolCaller{dry: true}, file, ""); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
})
|
|
t.Run("media credential failure", func(t *testing.T) {
|
|
if err := mediaCommand(t, &scriptedToolCaller{steps: []scriptedToolStep{{err: errors.New("credential")}}}, file, ""); err == nil {
|
|
t.Fatal("credential failure returned nil")
|
|
}
|
|
})
|
|
t.Run("media parse failure", func(t *testing.T) {
|
|
if err := mediaCommand(t, &scriptedToolCaller{steps: []scriptedToolStep{{text: `{}`}}}, file, ""); err == nil {
|
|
t.Fatal("parse failure returned nil")
|
|
}
|
|
})
|
|
t.Run("media put failure", func(t *testing.T) {
|
|
httpPutFile = func(context.Context, string, map[string]string, string, int64) error { return errors.New("put") }
|
|
if err := mediaCommand(t, &scriptedToolCaller{steps: []scriptedToolStep{{text: `{"uploadUrl":"https://upload","resourceId":"resource"}`}}}, file, ""); err == nil {
|
|
t.Fatal("media put failure returned nil")
|
|
}
|
|
})
|
|
t.Run("media insert failure", func(t *testing.T) {
|
|
httpPutFile = func(context.Context, string, map[string]string, string, int64) error { return nil }
|
|
caller := &scriptedToolCaller{steps: []scriptedToolStep{{text: `{"uploadUrl":"https://upload","resourceId":"resource"}`}, {err: errors.New("insert")}}}
|
|
if err := mediaCommand(t, caller, file, "text/plain"); err == nil {
|
|
t.Fatal("media insert failure returned nil")
|
|
}
|
|
})
|
|
t.Run("media insert response parse failure", func(t *testing.T) {
|
|
httpPutFile = func(context.Context, string, map[string]string, string, int64) error { return nil }
|
|
caller := &scriptedToolCaller{steps: []scriptedToolStep{
|
|
{text: `{"uploadUrl":"https://upload","resourceId":"resource"}`},
|
|
{text: `{`},
|
|
}}
|
|
if err := mediaCommand(t, caller, file, "text/plain"); err == nil {
|
|
t.Fatal("invalid media insert response returned nil")
|
|
}
|
|
})
|
|
t.Run("small image verifies src readback when upload also returns resource ID", func(t *testing.T) {
|
|
httpPutFile = func(context.Context, string, map[string]string, string, int64) error { return nil }
|
|
caller := &scriptedToolCaller{steps: []scriptedToolStep{
|
|
{text: `{"uploadUrl":"https://upload","resourceId":"resource","resourceUrl":"https://image"}`},
|
|
{text: `{"blockId":"media-block"}`},
|
|
{text: `{"blocks":[{"blockId":"media-block","jsonml":"[\"p\",{\"uuid\":\"media-block\"},[\"img\",{\"src\":\"https://image\"}]]"}],"hasMore":false}`},
|
|
}}
|
|
if err := mediaCommand(t, caller, file, "image/png"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if caller.calls != 3 {
|
|
t.Fatalf("media insert calls = %d, want credential, insert, and one readback", caller.calls)
|
|
}
|
|
if caller.args["format"] != "jsonml" {
|
|
t.Fatalf("media readback format = %#v, want jsonml", caller.args["format"])
|
|
}
|
|
})
|
|
t.Run("unrelated insert response IDs do not constrain media readback", func(t *testing.T) {
|
|
httpPutFile = func(context.Context, string, map[string]string, string, int64) error { return nil }
|
|
caller := &scriptedToolCaller{steps: []scriptedToolStep{
|
|
{text: `{"uploadUrl":"https://upload","resourceId":"resource","resourceUrl":"https://image"}`},
|
|
{text: `{"id":"document-id","operator":{"id":"operator-id"},"data":{"request":{"id":"request-id"}}}`},
|
|
{text: `{"blocks":[{"blockId":"media-block","jsonml":"[\"p\",{\"uuid\":\"media-block\"},[\"img\",{\"src\":\"https://image\"}]]"}],"hasMore":false}`},
|
|
}}
|
|
if err := mediaCommand(t, caller, file, "image/png"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if caller.calls != 3 {
|
|
t.Fatalf("media insert calls = %d, want credential, insert, and one readback", caller.calls)
|
|
}
|
|
})
|
|
t.Run("large image becomes attachment", func(t *testing.T) {
|
|
httpPutFile = func(context.Context, string, map[string]string, string, int64) error { return nil }
|
|
large := filepath.Join(t.TempDir(), "large.png")
|
|
if err := os.WriteFile(large, nil, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Truncate(large, 21*1024*1024); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
caller := &scriptedToolCaller{steps: []scriptedToolStep{
|
|
{text: `{"uploadUrl":"https://upload","resourceId":"resource","resourceUrl":"https://image"}`},
|
|
{text: `{}`},
|
|
{text: `{"blocks":[{"blockId":"media-block","element":{"attachment":{"resourceId":"resource"}}}],"hasMore":false}`},
|
|
}}
|
|
if err := mediaCommand(t, caller, large, "image/png"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestCrossPlatformCoverageDefaultDocHTTPTransportEdges(t *testing.T) {
|
|
file := filepath.Join(t.TempDir(), "body")
|
|
if err := os.WriteFile(file, []byte("body"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cancelled, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
if err := defaultHTTPPutFile(cancelled, "http://127.0.0.1:1", nil, file, 4); err == nil {
|
|
t.Fatal("cancelled upload returned nil")
|
|
}
|
|
if err := defaultHTTPGetFile(cancelled, "http://127.0.0.1:1", nil, filepath.Join(t.TempDir(), "out")); err == nil {
|
|
t.Fatal("cancelled download returned nil")
|
|
}
|
|
|
|
oldCreate, oldCopy := docCreateDestination, docCopyContent
|
|
t.Cleanup(func() { docCreateDestination, docCopyContent = oldCreate, oldCopy })
|
|
docCreateDestination = func(string) (*os.File, error) { return nil, errors.New("create") }
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
_, _ = io.WriteString(w, "body")
|
|
}))
|
|
t.Cleanup(server.Close)
|
|
if err := defaultHTTPGetFile(context.Background(), server.URL, nil, filepath.Join(t.TempDir(), "out")); err == nil {
|
|
t.Fatal("destination creation failure returned nil")
|
|
}
|
|
docCreateDestination = os.Create
|
|
docCopyContent = func(io.Writer, io.Reader) (int64, error) { return 0, errors.New("copy") }
|
|
if err := defaultHTTPGetFile(context.Background(), server.URL, nil, filepath.Join(t.TempDir(), "out")); err == nil {
|
|
t.Fatal("copy failure returned nil")
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageDocMediaReadbackDefensiveEdges(t *testing.T) {
|
|
testseam.Swap(t, &helperSleep, func(time.Duration) {})
|
|
testseam.Swap(t, &docMediaVerifyWait, func(context.Context, time.Duration) error { return nil })
|
|
ctx := context.Background()
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
steps []scriptedToolStep
|
|
}{
|
|
{"call failure", []scriptedToolStep{{err: errors.New("read")}}},
|
|
{"invalid json", []scriptedToolStep{{text: `{`}}},
|
|
{"missing blocks", []scriptedToolStep{{text: `{}`}}},
|
|
{"stalled page", []scriptedToolStep{{text: `{"blocks":[{"id":"a"}],"hasMore":true}`}, {text: `{"blocks":[{"id":"a"}],"hasMore":true}`}}},
|
|
{"empty continued page", []scriptedToolStep{{text: `{"blocks":[],"hasMore":true}`}}},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
installScriptedCaller(t, &scriptedToolCaller{steps: tc.steps})
|
|
if _, err := readAllDocBlocksForVerification(ctx, "node"); err == nil {
|
|
t.Fatal("defensive readback returned nil")
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("total count and nested payload", func(t *testing.T) {
|
|
installScriptedCaller(t, &scriptedToolCaller{steps: []scriptedToolStep{{text: `{"data":{"blocks":[{"id":"a"}],"totalCount":1}}`}}})
|
|
blocks, err := readAllDocBlocksForVerification(ctx, "node")
|
|
if err != nil || len(blocks) != 1 {
|
|
t.Fatalf("blocks=%#v err=%v", blocks, err)
|
|
}
|
|
})
|
|
|
|
t.Run("identical adjacent pages advance by requested indexes", func(t *testing.T) {
|
|
blocks := make([]any, 50)
|
|
for index := range blocks {
|
|
blocks[index] = map[string]any{"blockType": "paragraph"}
|
|
}
|
|
first, err := json.Marshal(map[string]any{"blocks": blocks, "hasMore": true, "totalCount": 100})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
second, err := json.Marshal(map[string]any{"blocks": blocks, "hasMore": false, "totalCount": 100})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
caller := &scriptedToolCaller{steps: []scriptedToolStep{{text: string(first)}, {text: string(second)}}}
|
|
installScriptedCaller(t, caller)
|
|
got, err := readAllDocBlocksForVerification(ctx, "node")
|
|
if err != nil || len(got) != 100 || caller.calls != 2 {
|
|
t.Fatalf("blocks=%d calls=%d err=%v, want 100 blocks from two pages", len(got), caller.calls, err)
|
|
}
|
|
})
|
|
|
|
t.Run("explicit has more overrides inconsistent total count", func(t *testing.T) {
|
|
caller := &scriptedToolCaller{steps: []scriptedToolStep{
|
|
{text: `{"blocks":[{"id":"first"}],"hasMore":true,"totalCount":1}`},
|
|
{text: `{"blocks":[{"id":"second"}],"hasMore":false,"totalCount":2}`},
|
|
}}
|
|
installScriptedCaller(t, caller)
|
|
got, err := readAllDocBlocksForVerification(ctx, "node")
|
|
if err != nil || len(got) != 2 || caller.calls != 2 {
|
|
t.Fatalf("blocks=%d calls=%d err=%v, want both explicitly advertised pages", len(got), caller.calls, err)
|
|
}
|
|
})
|
|
|
|
t.Run("page identity accepts nested and JSONML block IDs", func(t *testing.T) {
|
|
if got := docBlockPageIdentity([]any{map[string]any{"element": map[string]any{"blockId": "nested-block"}}}); got != `["nested-block"]` {
|
|
t.Fatalf("nested block page identity = %q", got)
|
|
}
|
|
if got := docBlockPageIdentity([]any{[]any{"p", map[string]any{"uuid": "jsonml-block"}}}); got != `["jsonml-block"]` {
|
|
t.Fatalf("JSONML block page identity = %q", got)
|
|
}
|
|
})
|
|
|
|
t.Run("unknown pagination short page", func(t *testing.T) {
|
|
installScriptedCaller(t, &scriptedToolCaller{steps: []scriptedToolStep{{text: `{"blocks":[{"id":"a"}]}`}}})
|
|
if blocks, err := readAllDocBlocksForVerification(ctx, "node"); err != nil || len(blocks) != 1 {
|
|
t.Fatalf("blocks=%#v err=%v", blocks, err)
|
|
}
|
|
})
|
|
|
|
t.Run("bounded retry failure", func(t *testing.T) {
|
|
installScriptedCaller(t, &scriptedToolCaller{steps: []scriptedToolStep{{text: `{"blocks":[],"hasMore":false}`}}})
|
|
if _, err := verifyInsertedDocMedia(ctx, "node", "", "missing", ""); err == nil {
|
|
t.Fatal("missing media unexpectedly verified")
|
|
}
|
|
})
|
|
|
|
t.Run("transient read failure retries", func(t *testing.T) {
|
|
caller := &scriptedToolCaller{steps: []scriptedToolStep{
|
|
{err: errors.New("temporary read failure")},
|
|
{text: `{"blocks":[{"blockId":"media-block","element":{"attachment":{"resourceId":"resource"}}}],"hasMore":false}`},
|
|
}}
|
|
installScriptedCaller(t, caller)
|
|
blockID, err := verifyInsertedDocMedia(ctx, "node", "media-block", "resource", "")
|
|
if err != nil || blockID != "media-block" || caller.calls != 2 {
|
|
t.Fatalf("blockID=%q calls=%d err=%v", blockID, caller.calls, err)
|
|
}
|
|
})
|
|
|
|
t.Run("block identity alone does not verify media", func(t *testing.T) {
|
|
installScriptedCaller(t, &scriptedToolCaller{steps: []scriptedToolStep{{text: `{"blocks":[{"blockId":"media-block","element":{"attachment":{"resourceId":"other-resource"}}}],"hasMore":false}`}}})
|
|
if _, err := verifyInsertedDocMedia(ctx, "node", "media-block", "resource", ""); err == nil {
|
|
t.Fatal("matching block ID with a different resource unexpectedly verified")
|
|
}
|
|
})
|
|
|
|
t.Run("block read safety limit", func(t *testing.T) {
|
|
steps := make([]scriptedToolStep, 100)
|
|
for index := range steps {
|
|
steps[index] = scriptedToolStep{text: fmt.Sprintf(`{"blocks":[{"id":"block-%d"}],"hasMore":true}`, index)}
|
|
}
|
|
installScriptedCaller(t, &scriptedToolCaller{steps: steps})
|
|
if _, err := readAllDocBlocksForVerification(ctx, "node"); err == nil {
|
|
t.Fatal("oversized block read returned nil")
|
|
}
|
|
})
|
|
|
|
if got := nestedDocMap(map[string]any{"result": map[string]any{"data": map[string]any{"ok": true}}}); got["ok"] != true {
|
|
t.Fatalf("nested map=%#v", got)
|
|
}
|
|
if nestedDocString(map[string]any{"x": []any{map[string]any{"id": " nested "}}}, "id") != "nested" || nestedDocString(3, "id") != "" {
|
|
t.Fatal("nested string traversal failed")
|
|
}
|
|
if got := nestedDocString(map[string]any{"z": map[string]any{"id": "last"}, "a": map[string]any{"id": "first"}}, "id"); got != "first" {
|
|
t.Fatalf("nested string traversal = %q, want deterministic key order", got)
|
|
}
|
|
if got := insertedDocBlockID(map[string]any{"id": "document", "result": map[string]any{"data": map[string]any{"blockId": " block "}}}); got != "block" {
|
|
t.Fatalf("trusted inserted block ID = %q, want block", got)
|
|
}
|
|
if got := insertedDocBlockID(map[string]any{"id": "document", "operator": map[string]any{"id": "operator"}, "data": map[string]any{"request": map[string]any{"id": "request"}}}); got != "" {
|
|
t.Fatalf("untrusted inserted block ID = %q, want empty", got)
|
|
}
|
|
blocks := []any{map[string]any{"id": "block", "resourceId": "rid"}, map[string]any{"id": "url-block", "resourceUrl": "https://media"}, map[string]any{"id": "src-block", "jsonml": `["p",{},["img",{"src":"https://image"}]]`}}
|
|
if findVerifiedMediaBlock(blocks, "block", "rid", "") != "block" || findVerifiedMediaBlock(blocks, "", "rid", "") != "block" || findVerifiedMediaBlock(blocks, "", "", "https://media") != "url-block" || findVerifiedMediaBlock(blocks, "src-block", "upload-resource", "https://image") != "src-block" || findVerifiedMediaBlock(blocks, "other-block", "upload-resource", "https://image") != "" || findVerifiedMediaBlock(blocks, "block", "wrong", "") != "" || findVerifiedMediaBlock(blocks, "", "missing", "") != "" {
|
|
t.Fatal("media block matching failed")
|
|
}
|
|
if findVerifiedMediaBlock([]any{map[string]any{"id": "bad-jsonml", "jsonml": `[`}}, "bad-jsonml", "", "https://image") != "" {
|
|
t.Fatal("invalid JSONML unexpectedly verified")
|
|
}
|
|
if got := docMediaReadbackValue("plain"); got != "plain" {
|
|
t.Fatalf("non-object media readback = %#v, want unchanged value", got)
|
|
}
|
|
for _, tc := range []struct {
|
|
value any
|
|
want bool
|
|
}{
|
|
{float64(3), true}, {float64(-1), false}, {1.5, false}, {3, true}, {-1, false}, {"3", false},
|
|
} {
|
|
_, ok := docNumberAsInt(tc.value)
|
|
if ok != tc.want {
|
|
t.Fatalf("docNumberAsInt(%#v) ok=%v want=%v", tc.value, ok, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageDocMediaReadbackStopsOnCancellation(t *testing.T) {
|
|
if err := waitForDocVerification(nil, time.Nanosecond); err != nil {
|
|
t.Fatalf("completed verification wait = %v", err)
|
|
}
|
|
cancelled, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
if err := waitForDocVerification(cancelled, time.Hour); !errors.Is(err, context.Canceled) {
|
|
t.Fatalf("cancelled verification wait = %v, want context.Canceled", err)
|
|
}
|
|
|
|
caller := &scriptedToolCaller{steps: []scriptedToolStep{{text: `{"blocks":[],"hasMore":false}`}}}
|
|
installScriptedCaller(t, caller)
|
|
if _, err := verifyInsertedDocMedia(cancelled, "node", "", "missing", ""); !errors.Is(err, context.Canceled) {
|
|
t.Fatalf("cancelled media verification = %v, want context.Canceled", err)
|
|
}
|
|
if caller.calls != 1 {
|
|
t.Fatalf("cancelled media verification calls = %d, want 1", caller.calls)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageDocCreateUpdateAndBlockCommandEdges(t *testing.T) {
|
|
oldDeps, oldArgs, oldPut, oldGet := deps, os.Args, httpPutFile, httpGetFile
|
|
t.Cleanup(func() {
|
|
deps, os.Args, httpPutFile, httpGetFile = oldDeps, oldArgs, oldPut, oldGet
|
|
})
|
|
os.Args = []string{"dws", "doc", "--yes"}
|
|
httpPutFile = func(context.Context, string, map[string]string, string, int64) error { return nil }
|
|
httpGetFile = func(context.Context, string, map[string]string, string) error { return nil }
|
|
|
|
type commandCase struct {
|
|
name string
|
|
steps []scriptedToolStep
|
|
args []string
|
|
}
|
|
docJSONML := `["root",{},["p",{},"hello"]]`
|
|
nodeJSONML := `["p",{},"hello"]`
|
|
cases := []commandCase{
|
|
{"create numeric folder", nil, []string{"create", "--name=n", "--folder=123"}},
|
|
{"create missing content file", nil, []string{"create", "--name=n", "--content-file=missing"}},
|
|
{"create markdown that resembles jsonml", []scriptedToolStep{{text: `{}`}}, []string{"create", "--name=n", `--content=["p",{},"hello"]`}},
|
|
{"create invalid jsonml", nil, []string{"create", "--name=n", "--content-format=jsonml", "--content={"}},
|
|
{"create jsonml call failure", []scriptedToolStep{{err: errors.New("create")}}, []string{"create", "--name=n", "--content-format=jsonml", "--content=" + docJSONML}},
|
|
{"create jsonml missing node", []scriptedToolStep{{text: `{}`}}, []string{"create", "--name=n", "--content-format=jsonml", "--content=" + docJSONML}},
|
|
{"create jsonml success", []scriptedToolStep{{text: `{"nodeId":"node"}`}, {text: `{}`}}, []string{"create", "--name=n", "--folder=folder", "--workspace=workspace", "--content-format=jsonml", "--content=" + docJSONML}},
|
|
{"update missing content file", nil, []string{"update", "--node=node", "--content-file=missing", "--mode=append"}},
|
|
{"update empty content", nil, []string{"update", "--node=node", "--content="}},
|
|
{"update missing mode", nil, []string{"update", "--node=node", "--content=body", "--mode="}},
|
|
{"update overwrite preview", []scriptedToolStep{{text: `{"markdown":"old"}`}}, []string{"update", "--node=node", "--content=new", "--mode=overwrite", "--dry-run"}},
|
|
{"update overwrite preview failure", []scriptedToolStep{{err: errors.New("read")}}, []string{"update", "--node=node", "--content=new", "--mode=overwrite", "--dry-run"}},
|
|
{"update overwrite needs yes", nil, []string{"update", "--node=node", "--content=new", "--mode=overwrite"}},
|
|
{"update markdown resembles jsonml", []scriptedToolStep{{text: `{}`}}, []string{"update", "--node=node", `--content=["p",{},"hello"]`, "--mode=append"}},
|
|
{"update jsonml append", nil, []string{"update", "--node=node", "--content-format=jsonml", "--content=" + docJSONML, "--mode=append"}},
|
|
{"update invalid jsonml", nil, []string{"update", "--node=node", "--content-format=jsonml", "--content={", "--mode=overwrite", "--yes"}},
|
|
{"update jsonml revision", []scriptedToolStep{{text: `{}`}}, []string{"update", "--node=node", "--content-format=jsonml", "--content=" + docJSONML, "--mode=overwrite", "--revision=2", "--yes"}},
|
|
{"update markdown index", []scriptedToolStep{{text: `{}`}}, []string{"update", "--node=node", "--content=body", "--mode=append", "--index=2"}},
|
|
{"file create numeric folder", nil, []string{"file", "create", "--name=n", "--type=adoc", "--folder=123"}},
|
|
{"folder create numeric folder", nil, []string{"folder", "create", "--name=n", "--folder=123"}},
|
|
{"block insert sniff", []scriptedToolStep{{text: `{}`}}, []string{"block", "insert", "--node=node", `--element=["p",{},"hello"]`}},
|
|
{"block insert invalid jsonml", nil, []string{"block", "insert", "--node=node", "--content-format=jsonml", "--element={"}},
|
|
{"block insert jsonml options", []scriptedToolStep{{text: `{}`}}, []string{"block", "insert", "--node=node", "--content-format=jsonml", "--element=" + nodeJSONML, "--ref-block=ref", "--parent-block=parent", "--index=2"}},
|
|
{"block insert normal options", []scriptedToolStep{{text: `{}`}}, []string{"block", "insert", "--node=node", "--text=body", "--index=2", "--where=before", "--ref-block=ref"}},
|
|
{"block update sniff", []scriptedToolStep{{text: `{}`}}, []string{"block", "update", "--node=node", "--block-id=block", `--element=["p",{},"hello"]`}},
|
|
{"block update invalid jsonml", nil, []string{"block", "update", "--node=node", "--block-id=block", "--content-format=jsonml", "--element={"}},
|
|
{"block update jsonml", []scriptedToolStep{{text: `{}`}}, []string{"block", "update", "--node=node", "--block-id=block", "--content-format=jsonml", "--element=" + nodeJSONML}},
|
|
{"block update normal", []scriptedToolStep{{text: `{}`}}, []string{"block", "update", "--node=node", "--block-id=block", "--heading=Title", "--level=2"}},
|
|
{"inline comment missing offsets", nil, []string{"comment", "create-inline", "--node=node", "--block-id=block", "--content=note"}},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if err := runDocCoverageCommand(t, &scriptedToolCaller{steps: tc.steps}, tc.args...); err != nil {
|
|
t.Logf("command returned: %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
for _, value := range []string{"plain", "# Other\nbody", "# Name", "# **Name** ###\n\nbody"} {
|
|
_ = stripDuplicateTitle(value, "Name")
|
|
}
|
|
for _, value := range []string{"", " Name ### ", "**Bold**", "__Under__ ~~Strike~~ `Code`"} {
|
|
_ = normalizeHeadingText(value)
|
|
}
|
|
for _, name := range []string{"file.pdf", "file.md", "file.unknown"} {
|
|
_ = inferMimeType(name)
|
|
}
|
|
_ = extractMarkdownField("not-json")
|
|
|
|
newBlockCommand := func() *cobra.Command {
|
|
root := newDocCommand()
|
|
cmd, _, _ := root.Find([]string{"block", "insert"})
|
|
return cmd
|
|
}
|
|
for _, tc := range []struct {
|
|
flag, value string
|
|
}{
|
|
{"element", "{"},
|
|
{"element", `{"blockType":"paragraph"}`},
|
|
{"heading", "Heading"},
|
|
{"text", "Body"},
|
|
} {
|
|
cmd := newBlockCommand()
|
|
_ = cmd.Flags().Set(tc.flag, tc.value)
|
|
_, _ = buildBlockElement(cmd)
|
|
}
|
|
cmd := newBlockCommand()
|
|
_ = cmd.Flags().Set("heading", "Heading")
|
|
_ = cmd.Flags().Set("level", "99")
|
|
_, _ = buildBlockElement(cmd)
|
|
_, _ = buildBlockElement(newBlockCommand())
|
|
}
|
|
|
|
func TestCrossPlatformCoverageDocDestructiveCancellationEdges(t *testing.T) {
|
|
oldDeps, oldArgs, oldStdin := deps, os.Args, os.Stdin
|
|
t.Cleanup(func() { deps, os.Args, os.Stdin = oldDeps, oldArgs, oldStdin })
|
|
os.Args = []string{"dws", "doc"}
|
|
input, err := os.CreateTemp(t.TempDir(), "answers")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = input.Close() })
|
|
_, _ = input.WriteString(strings.Repeat("no\n", 4))
|
|
_, _ = input.Seek(0, 0)
|
|
os.Stdin = input
|
|
for _, args := range [][]string{
|
|
{"block", "delete", "--node=node", "--block-id=block"},
|
|
{"delete", "--node=node"},
|
|
} {
|
|
_ = runDocCoverageCommand(t, &scriptedToolCaller{}, args...)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageDocExportImportCommandEdges(t *testing.T) {
|
|
oldDeps, oldArgs, oldPut, oldGet, oldSleep, oldAfter := deps, os.Args, httpPutFile, httpGetFile, helperSleep, helperAfter
|
|
t.Cleanup(func() {
|
|
deps, os.Args, httpPutFile, httpGetFile, helperSleep, helperAfter = oldDeps, oldArgs, oldPut, oldGet, oldSleep, oldAfter
|
|
})
|
|
os.Args = []string{"dws", "doc", "--yes"}
|
|
helperSleep = func(time.Duration) {}
|
|
helperAfter = func(time.Duration) <-chan time.Time {
|
|
ch := make(chan time.Time, 1)
|
|
ch <- time.Now()
|
|
return ch
|
|
}
|
|
httpPutFile = func(context.Context, string, map[string]string, string, int64) error { return nil }
|
|
httpGetFile = func(context.Context, string, map[string]string, string) error { return nil }
|
|
|
|
run := func(t *testing.T, steps []scriptedToolStep, dry bool, args ...string) error {
|
|
t.Helper()
|
|
return runDocCoverageCommand(t, &scriptedToolCaller{steps: steps, dry: dry}, args...)
|
|
}
|
|
outDir := t.TempDir()
|
|
exportCases := []struct {
|
|
name string
|
|
steps []scriptedToolStep
|
|
dry bool
|
|
args []string
|
|
}{
|
|
{"legacy md", nil, true, []string{"export", "--node=node", "--output=out", "--export-format=", "--format=md"}},
|
|
{"legacy global ignored", nil, true, []string{"export", "--node=node", "--output=out", "--export-format=", "--format=json"}},
|
|
{"invalid format", nil, false, []string{"export", "--node=node", "--output=out", "--export-format=zip"}},
|
|
{"dry run", nil, true, []string{"export", "--node=node", "--output=out", "--export-format=pdf"}},
|
|
{"submit failure", []scriptedToolStep{{err: errors.New("submit")}}, false, []string{"export", "--node=node", "--output=out"}},
|
|
{"submit invalid", []scriptedToolStep{{text: `{`}}, false, []string{"export", "--node=node", "--output=out"}},
|
|
{"submit missing job", []scriptedToolStep{{text: `{}`}}, false, []string{"export", "--node=node", "--output=out"}},
|
|
{"poll failure", []scriptedToolStep{{text: `{"jobId":"job"}`}, {err: errors.New("poll")}}, false, []string{"export", "--node=node", "--output=out"}},
|
|
{"download directory without extension", []scriptedToolStep{{text: `{"jobId":"job"}`}, {text: `{"status":"SUCCESS","downloadUrl":"https://example.test/download"}`}}, false, []string{"export", "--node=node", "--output=" + outDir}},
|
|
{"download directory", []scriptedToolStep{{text: `{"jobId":"job"}`}, {text: `{"status":"SUCCESS","downloadUrl":"https://example.test/name.txt"}`}}, false, []string{"export", "--node=node", "--output=" + outDir, "--export-format=pdf"}},
|
|
}
|
|
for _, tc := range exportCases {
|
|
t.Run("export "+tc.name, func(t *testing.T) { _ = run(t, tc.steps, tc.dry, tc.args...) })
|
|
}
|
|
httpGetFile = func(context.Context, string, map[string]string, string) error { return errors.New("download") }
|
|
_ = run(t, []scriptedToolStep{{text: `{"jobId":"job"}`}, {text: `{"status":"SUCCESS","downloadUrl":"https://example.test/file.docx"}`}}, false, "export", "--node=node", "--output=out")
|
|
httpGetFile = func(context.Context, string, map[string]string, string) error { return nil }
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
steps []scriptedToolStep
|
|
dry bool
|
|
}{
|
|
{"dry", nil, true},
|
|
{"error", []scriptedToolStep{{err: errors.New("query")}}, false},
|
|
{"invalid", []scriptedToolStep{{text: `{`}}, false},
|
|
{"success", []scriptedToolStep{{text: `{"status":"SUCCESS"}`}}, false},
|
|
{"processing", []scriptedToolStep{{text: `{"status":"PROCESSING"}`}}, false},
|
|
{"failed message", []scriptedToolStep{{text: `{"status":"FAILED","message":"bad"}`}}, false},
|
|
{"failed empty", []scriptedToolStep{{text: `{"status":"FAILED"}`}}, false},
|
|
} {
|
|
t.Run("export get "+tc.name, func(t *testing.T) {
|
|
_ = run(t, tc.steps, tc.dry, "export", "get", "--job-id=job")
|
|
})
|
|
}
|
|
|
|
valid := filepath.Join(t.TempDir(), "report.md")
|
|
empty := filepath.Join(t.TempDir(), "empty.md")
|
|
large := filepath.Join(t.TempDir(), "large.md")
|
|
unsupported := filepath.Join(t.TempDir(), "file.bin")
|
|
for path, data := range map[string][]byte{valid: []byte("body"), empty: nil, unsupported: []byte("body")} {
|
|
if err := os.WriteFile(path, data, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := os.WriteFile(large, nil, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Truncate(large, 21*1024*1024); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = run(t, nil, false, "import", "--file="+t.TempDir())
|
|
_ = run(t, nil, false, "import", "--file="+large)
|
|
_ = run(t, nil, false, "import", "--file="+empty)
|
|
_ = run(t, nil, false, "import", "--file="+unsupported)
|
|
_ = run(t, nil, true, "import", valid, "--folder=folder", "--workspace=workspace")
|
|
_ = run(t, []scriptedToolStep{{err: errors.New("session")}}, false, "import", "--file="+valid)
|
|
_ = run(t, []scriptedToolStep{{text: `{`}}, false, "import", "--file="+valid)
|
|
_ = run(t, []scriptedToolStep{{text: `{}`}}, false, "import", "--file="+valid)
|
|
httpPutFile = func(context.Context, string, map[string]string, string, int64) error { return errors.New("put") }
|
|
_ = run(t, []scriptedToolStep{{text: `{"sessionId":"session","uploadUrl":"https://upload"}`}}, false, "import", "--file="+valid)
|
|
httpPutFile = func(context.Context, string, map[string]string, string, int64) error { return nil }
|
|
_ = run(t, []scriptedToolStep{{text: `{"sessionId":"session","uploadUrl":"https://upload"}`}, {err: errors.New("confirm")}}, false, "import", "--file="+valid)
|
|
_ = run(t, []scriptedToolStep{{text: `{"sessionId":"session","uploadUrl":"https://upload"}`}, {text: `{`}}, false, "import", "--file="+valid)
|
|
_ = run(t, []scriptedToolStep{{text: `{"sessionId":"session","uploadUrl":"https://upload"}`}, {text: `{}`}}, false, "import", "--file="+valid)
|
|
_ = run(t, []scriptedToolStep{{text: `{"sessionId":"session","uploadUrl":"https://upload"}`}, {text: `{"taskId":"task"}`}, {err: errors.New("poll")}}, false, "import", "--file="+valid)
|
|
_ = run(t, []scriptedToolStep{{text: `{"sessionId":"session","uploadUrl":"https://upload"}`}, {text: `{"taskId":"task"}`}, {text: `{"status":"completed","documentUrl":"url","documentName":"name","documentType":"doc"}`}}, false, "import", "--file="+valid)
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
steps []scriptedToolStep
|
|
dry bool
|
|
}{
|
|
{"dry", nil, true},
|
|
{"error", []scriptedToolStep{{err: errors.New("query")}}, false},
|
|
{"invalid", []scriptedToolStep{{text: `{`}}, false},
|
|
{"completed", []scriptedToolStep{{text: `{"status":"completed"}`}}, false},
|
|
{"processing", []scriptedToolStep{{text: `{"status":"processing"}`}}, false},
|
|
{"failed message", []scriptedToolStep{{text: `{"status":"failed","message":"bad"}`}}, false},
|
|
{"failed empty", []scriptedToolStep{{text: `{"status":"failed"}`}}, false},
|
|
} {
|
|
t.Run("import get "+tc.name, func(t *testing.T) {
|
|
_ = run(t, tc.steps, tc.dry, "import", "get", "--task-id=task")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageDocVersionRevertCommandEdges(t *testing.T) {
|
|
oldDeps, oldArgs, oldStdin := deps, os.Args, os.Stdin
|
|
t.Cleanup(func() { deps, os.Args, os.Stdin = oldDeps, oldArgs, oldStdin })
|
|
|
|
os.Args = []string{"dws", "doc", "--yes"}
|
|
_ = runDocCoverageCommand(t, &scriptedToolCaller{steps: []scriptedToolStep{{err: errors.New("list")}}}, "version", "revert", "--node=node", "--version=3")
|
|
_ = runDocCoverageCommand(t, &scriptedToolCaller{steps: []scriptedToolStep{{text: `{"versions":[]}`}}}, "version", "revert", "--node=node", "--version=3")
|
|
_ = runDocCoverageCommand(t, &scriptedToolCaller{steps: []scriptedToolStep{{text: `{"versions":[{"version":3}]}`}, {text: `{}`}}}, "version", "revert", "--node=node", "--version=3")
|
|
|
|
os.Args = []string{"dws", "doc"}
|
|
input, err := os.CreateTemp(t.TempDir(), "answer")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = input.Close() })
|
|
_, _ = input.WriteString("no\n")
|
|
_, _ = input.Seek(0, 0)
|
|
os.Stdin = input
|
|
_ = runDocCoverageCommand(t, &scriptedToolCaller{steps: []scriptedToolStep{{text: `{"versions":[{"version":3}]}`}}}, "version", "revert", "--node=node", "--version=3")
|
|
}
|