|
1 | 1 | package cmd |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "bytes" |
| 5 | + "encoding/json" |
4 | 6 | "fmt" |
5 | 7 | "io" |
6 | 8 | "os" |
| 9 | + "path" |
7 | 10 | "strings" |
8 | 11 | "testing" |
| 12 | + "time" |
9 | 13 |
|
| 14 | + "github.com/dropbox/dbxcli/internal/output" |
10 | 15 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox" |
11 | 16 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/files" |
| 17 | + "github.com/spf13/cobra" |
12 | 18 | ) |
13 | 19 |
|
14 | 20 | func stubFilesClient(t *testing.T, client files.Client) { |
@@ -194,3 +200,122 @@ func TestCpCopyErrorIncludesAPIError(t *testing.T) { |
194 | 200 | t.Errorf("stderr still contains formatted relocation arg: %q", stderr) |
195 | 201 | } |
196 | 202 | } |
| 203 | + |
| 204 | +func TestCpJSONOutputsRelocationResults(t *testing.T) { |
| 205 | + stubFilesClient(t, &mockFilesClient{ |
| 206 | + getMetadataFn: func(arg *files.GetMetadataArg) (files.IsMetadata, error) { |
| 207 | + return nil, fmt.Errorf("path/not_found/") |
| 208 | + }, |
| 209 | + copyV2Fn: func(arg *files.RelocationArg) (*files.RelocationResult, error) { |
| 210 | + metadata := files.NewFileMetadata("file-copy.txt", "id:file-copy", time.Time{}, time.Time{}, "rev-copy", 42) |
| 211 | + metadata.PathDisplay = arg.ToPath |
| 212 | + metadata.PathLower = strings.ToLower(arg.ToPath) |
| 213 | + return files.NewRelocationResult(metadata), nil |
| 214 | + }, |
| 215 | + }) |
| 216 | + |
| 217 | + var stdout bytes.Buffer |
| 218 | + cmd := newRelocationTestCommand(&stdout, nil) |
| 219 | + |
| 220 | + if err := cp(cmd, []string{"/src/file.txt", "/dest/file-copy.txt"}); err != nil { |
| 221 | + t.Fatalf("cp error: %v", err) |
| 222 | + } |
| 223 | + |
| 224 | + got := decodeRelocationOutput(t, stdout.Bytes()) |
| 225 | + if len(got.Results) != 1 { |
| 226 | + t.Fatalf("results = %d, want 1", len(got.Results)) |
| 227 | + } |
| 228 | + result := got.Results[0] |
| 229 | + if result.Input.FromPath != "/src/file.txt" || result.Input.ToPath != "/dest/file-copy.txt" { |
| 230 | + t.Fatalf("input = %#v, want source and destination", result.Input) |
| 231 | + } |
| 232 | + if result.Result.Type != "file" || result.Result.PathDisplay != "/dest/file-copy.txt" { |
| 233 | + t.Fatalf("result = %#v, want copied file metadata", result.Result) |
| 234 | + } |
| 235 | + if result.Result.Size == nil || *result.Result.Size != 42 { |
| 236 | + t.Fatalf("size = %#v, want 42", result.Result.Size) |
| 237 | + } |
| 238 | +} |
| 239 | + |
| 240 | +func TestCpJSONMultipleSourcesOutputsMultipleResults(t *testing.T) { |
| 241 | + stubFilesClient(t, &mockFilesClient{ |
| 242 | + copyV2Fn: func(arg *files.RelocationArg) (*files.RelocationResult, error) { |
| 243 | + name := path.Base(arg.ToPath) |
| 244 | + metadata := files.NewFileMetadata(name, "id:"+name, time.Time{}, time.Time{}, "rev", 1) |
| 245 | + metadata.PathDisplay = arg.ToPath |
| 246 | + metadata.PathLower = strings.ToLower(arg.ToPath) |
| 247 | + return files.NewRelocationResult(metadata), nil |
| 248 | + }, |
| 249 | + }) |
| 250 | + |
| 251 | + var stdout bytes.Buffer |
| 252 | + cmd := newRelocationTestCommand(&stdout, nil) |
| 253 | + |
| 254 | + if err := cp(cmd, []string{"/src/a.txt", "/src/b.txt", "/dest"}); err != nil { |
| 255 | + t.Fatalf("cp error: %v", err) |
| 256 | + } |
| 257 | + |
| 258 | + got := decodeRelocationOutput(t, stdout.Bytes()) |
| 259 | + if len(got.Results) != 2 { |
| 260 | + t.Fatalf("results = %d, want 2", len(got.Results)) |
| 261 | + } |
| 262 | + if got.Results[0].Input.ToPath != "/dest/a.txt" || got.Results[1].Input.ToPath != "/dest/b.txt" { |
| 263 | + t.Fatalf("results = %#v, want folder destinations", got.Results) |
| 264 | + } |
| 265 | +} |
| 266 | + |
| 267 | +func TestCpJSONErrorUsesCommandStderr(t *testing.T) { |
| 268 | + stubFilesClient(t, &mockFilesClient{ |
| 269 | + getMetadataFn: func(arg *files.GetMetadataArg) (files.IsMetadata, error) { |
| 270 | + return nil, fmt.Errorf("path/not_found/") |
| 271 | + }, |
| 272 | + copyV2Fn: func(arg *files.RelocationArg) (*files.RelocationResult, error) { |
| 273 | + return nil, fmt.Errorf("path/malformed_path/") |
| 274 | + }, |
| 275 | + }) |
| 276 | + |
| 277 | + var stdout bytes.Buffer |
| 278 | + var stderr bytes.Buffer |
| 279 | + cmd := newRelocationTestCommand(&stdout, &stderr) |
| 280 | + |
| 281 | + err := cp(cmd, []string{"/src/file.txt", "/dest/file.txt"}) |
| 282 | + if err == nil { |
| 283 | + t.Fatal("expected cp error") |
| 284 | + } |
| 285 | + if stdout.String() != "" { |
| 286 | + t.Fatalf("stdout = %q, want empty", stdout.String()) |
| 287 | + } |
| 288 | + if !strings.Contains(stderr.String(), `copy "/src/file.txt" to "/dest/file.txt": path/malformed_path/`) { |
| 289 | + t.Fatalf("stderr = %q, want copy API error", stderr.String()) |
| 290 | + } |
| 291 | +} |
| 292 | + |
| 293 | +func TestCpCommandSupportsStructuredOutput(t *testing.T) { |
| 294 | + if !commandSupportsStructuredOutput(cpCmd) { |
| 295 | + t.Fatal("cp should support structured output") |
| 296 | + } |
| 297 | +} |
| 298 | + |
| 299 | +func newRelocationTestCommand(stdout, stderr *bytes.Buffer) *cobra.Command { |
| 300 | + cmd := &cobra.Command{} |
| 301 | + cmd.Flags().String(outputFlag, string(output.FormatText), "") |
| 302 | + if err := cmd.Flags().Set(outputFlag, string(output.FormatJSON)); err != nil { |
| 303 | + panic(err) |
| 304 | + } |
| 305 | + if stdout != nil { |
| 306 | + cmd.SetOut(stdout) |
| 307 | + } |
| 308 | + if stderr != nil { |
| 309 | + cmd.SetErr(stderr) |
| 310 | + } |
| 311 | + return cmd |
| 312 | +} |
| 313 | + |
| 314 | +func decodeRelocationOutput(t *testing.T, data []byte) relocationOutput { |
| 315 | + t.Helper() |
| 316 | + var got relocationOutput |
| 317 | + if err := json.Unmarshal(data, &got); err != nil { |
| 318 | + t.Fatalf("decode JSON output: %v\noutput: %s", err, string(data)) |
| 319 | + } |
| 320 | + return got |
| 321 | +} |
0 commit comments