Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions cmd/mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type mockFilesClient struct {
listFolderContinueFn func(arg *files.ListFolderContinueArg) (*files.ListFolderResult, error)
moveV2Fn func(arg *files.RelocationArg) (*files.RelocationResult, error)
permanentlyDeleteFn func(arg *files.DeleteArg) error
searchV2Fn func(arg *files.SearchV2Arg) (*files.SearchV2Result, error)
searchContinueV2Fn func(arg *files.SearchV2ContinueArg) (*files.SearchV2Result, error)
}

func (m *mockFilesClient) Download(arg *files.DownloadArg) (*files.FileMetadata, io.ReadCloser, error) {
Expand Down Expand Up @@ -242,9 +244,15 @@ func (m *mockFilesClient) Search(arg *files.SearchArg) (*files.SearchResult, err
return nil, nil
}
func (m *mockFilesClient) SearchV2(arg *files.SearchV2Arg) (*files.SearchV2Result, error) {
if m.searchV2Fn != nil {
return m.searchV2Fn(arg)
}
return nil, nil
}
func (m *mockFilesClient) SearchContinueV2(arg *files.SearchV2ContinueArg) (*files.SearchV2Result, error) {
if m.searchContinueV2Fn != nil {
return m.searchContinueV2Fn(arg)
}
return nil, nil
}
func (m *mockFilesClient) TagsAdd(arg *files.AddTagArg) error { return nil }
Expand Down
48 changes: 34 additions & 14 deletions cmd/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"errors"
"fmt"
"io"
"os"
"strings"
"text/tabwriter"

Expand All @@ -31,7 +30,6 @@ func search(cmd *cobra.Command, args []string) (err error) {
return errors.New("`search` requires a `query` argument")
}

// Parse path scope, if provided.
var scope string
if len(args) == 2 {
scope = args[1]
Expand All @@ -40,33 +38,55 @@ func search(cmd *cobra.Command, args []string) (err error) {
}
}

arg := files.NewSearchArg(scope, args[0])
arg := files.NewSearchV2Arg(args[0])
if scope != "" {
opts := files.NewSearchOptions()
opts.Path = scope
arg.Options = opts
}

dbx := files.New(config)
res, err := dbx.Search(arg)
dbx := filesNewFunc(config)
res, err := dbx.SearchV2(arg)
if err != nil {
return
return err
}

var entries []files.IsMetadata
for _, m := range res.Matches {
if m.Metadata != nil && m.Metadata.Metadata != nil {
entries = append(entries, m.Metadata.Metadata)
}
}

for res.HasMore {
contArg := files.NewSearchV2ContinueArg(res.Cursor)
res, err = dbx.SearchContinueV2(contArg)
if err != nil {
return err
}
for _, m := range res.Matches {
if m.Metadata != nil && m.Metadata.Metadata != nil {
entries = append(entries, m.Metadata.Metadata)
}
}
}

opts := parseLsOptions(cmd)
sortEntries(entries, opts)

return renderSearchResults(os.Stdout, res, opts)
return commandOutput(cmd).RenderText(func(w io.Writer) error {
return renderSearchResults(w, entries, opts)
})
}

func renderSearchResults(out io.Writer, res *files.SearchResult, opts listOptions) error {
func renderSearchResults(out io.Writer, entries []files.IsMetadata, opts listOptions) error {
w := new(tabwriter.Writer)
w.Init(out, 4, 8, 1, ' ', 0)

if opts.long {
_, _ = fmt.Fprint(w, "Revision\tSize\tLast modified\tPath\n")
}

entries := make([]files.IsMetadata, 0, len(res.Matches))
for _, m := range res.Matches {
entries = append(entries, m.Metadata)
}
sortEntries(entries, opts)

for _, entry := range entries {
switch f := entry.(type) {
case *files.FileMetadata:
Expand Down
109 changes: 102 additions & 7 deletions cmd/search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
"strings"
"testing"

"github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox"
"github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/files"
"github.com/spf13/cobra"
)

func TestSearchArgValidation(t *testing.T) {
Expand All @@ -23,17 +25,17 @@ func TestSearchPathScopeValidation(t *testing.T) {
}

func TestRenderSearchResultsSeparatesMatchesWithNewlines(t *testing.T) {
res := files.NewSearchResult([]*files.SearchMatch{
files.NewSearchMatch(nil, &files.FileMetadata{
entries := []files.IsMetadata{
&files.FileMetadata{
Metadata: files.Metadata{PathDisplay: "/first.txt"},
}),
files.NewSearchMatch(nil, &files.FolderMetadata{
},
&files.FolderMetadata{
Metadata: files.Metadata{PathDisplay: "/second"},
}),
}, false, 0)
},
}

var out bytes.Buffer
if err := renderSearchResults(&out, res, listOptions{long: false}); err != nil {
if err := renderSearchResults(&out, entries, listOptions{long: false}); err != nil {
t.Fatalf("renderSearchResults returned error: %v", err)
}

Expand All @@ -49,3 +51,96 @@ func TestRenderSearchResultsSeparatesMatchesWithNewlines(t *testing.T) {
t.Errorf("second rendered match = %q, want %q", got, want)
}
}

func TestRenderSearchResultsLongModeIncludesHeader(t *testing.T) {
entries := []files.IsMetadata{
&files.FileMetadata{
Metadata: files.Metadata{PathDisplay: "/first.txt"},
Rev: "abc123",
Size: 42,
},
}

var out bytes.Buffer
if err := renderSearchResults(&out, entries, listOptions{long: true}); err != nil {
t.Fatalf("renderSearchResults returned error: %v", err)
}

got := out.String()
for _, want := range []string{"Revision", "Size", "Last modified", "Path", "abc123", "/first.txt"} {
if !strings.Contains(got, want) {
t.Errorf("output = %q, want to contain %q", got, want)
}
}
}

func TestSearchUsesSearchV2AndCommandOutput(t *testing.T) {
cmd, stdout := testSearchCmd()
var firstArg *files.SearchV2Arg
var continueCursor string

mock := &mockFilesClient{
searchV2Fn: func(arg *files.SearchV2Arg) (*files.SearchV2Result, error) {
firstArg = arg
res := files.NewSearchV2Result([]*files.SearchMatchV2{
searchMatch(&files.FileMetadata{
Metadata: files.Metadata{PathDisplay: "/docs/first.txt"},
}),
}, true)
res.Cursor = "cursor-1"
return res, nil
},
searchContinueV2Fn: func(arg *files.SearchV2ContinueArg) (*files.SearchV2Result, error) {
continueCursor = arg.Cursor
return files.NewSearchV2Result([]*files.SearchMatchV2{
searchMatch(&files.FolderMetadata{
Metadata: files.Metadata{PathDisplay: "/docs/second"},
}),
}, false), nil
},
}
stubFilesClient(t, mock)

if err := search(cmd, []string{"needle", "/docs"}); err != nil {
t.Fatalf("search error: %v", err)
}

if firstArg == nil {
t.Fatal("SearchV2 was not called")
}
if firstArg.Query != "needle" {
t.Errorf("query = %q, want %q", firstArg.Query, "needle")
}
if firstArg.Options == nil || firstArg.Options.Path != "/docs" {
t.Fatalf("options path = %#v, want /docs", firstArg.Options)
}
if continueCursor != "cursor-1" {
t.Errorf("continue cursor = %q, want cursor-1", continueCursor)
}

got := stdout.String()
for _, want := range []string{"/docs/first.txt", "/docs/second"} {
if !strings.Contains(got, want) {
t.Errorf("stdout = %q, want to contain %q", got, want)
}
}
}

func testSearchCmd() (*cobra.Command, *bytes.Buffer) {
var stdout bytes.Buffer
cmd := &cobra.Command{Use: "search"}
cmd.SetOut(&stdout)
cmd.Flags().BoolP("long", "l", false, "")
cmd.Flags().String("sort", "", "")
cmd.Flags().BoolP("reverse", "r", false, "")
cmd.Flags().String("time", "server", "")
cmd.Flags().String("time-format", "", "")
return cmd, &stdout
}

func searchMatch(metadata files.IsMetadata) *files.SearchMatchV2 {
return files.NewSearchMatchV2(&files.MetadataV2{
Tagged: dropbox.Tagged{Tag: files.MetadataV2Metadata},
Metadata: metadata,
})
}