github.com/Files-com/files-sdk-go/v3@v3.1.81/file/downloader_test.go (about) 1 package file 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "io/fs" 8 "os" 9 "path/filepath" 10 "sync" 11 "testing" 12 "testing/fstest" 13 "time" 14 15 files_sdk "github.com/Files-com/files-sdk-go/v3" 16 "github.com/Files-com/files-sdk-go/v3/file/manager" 17 "github.com/Files-com/files-sdk-go/v3/file/status" 18 "github.com/Files-com/files-sdk-go/v3/lib" 19 "github.com/samber/lo" 20 "github.com/stretchr/testify/assert" 21 "github.com/stretchr/testify/require" 22 ) 23 24 type ReporterCall struct { 25 JobFile 26 err error 27 } 28 29 type TestSetup struct { 30 files []Entity 31 reporterCalls []ReporterCall 32 fstest.MapFS 33 DownloaderParams 34 rootDestination string 35 tempDir string 36 files_sdk.Config 37 } 38 39 func NewTestSetup() *TestSetup { 40 t := &TestSetup{Config: files_sdk.Config{}.Init()} 41 t.MapFS = make(fstest.MapFS) 42 err := t.TempDir() 43 if err != nil { 44 panic(err) 45 } 46 return t 47 } 48 49 func (setup *TestSetup) Reporter() EventsReporter { 50 m := sync.Mutex{} 51 52 callback := func(status JobFile) { 53 m.Lock() 54 setup.reporterCalls = append(setup.reporterCalls, ReporterCall{JobFile: status}) 55 m.Unlock() 56 } 57 58 return CreateFileEvents(callback, append(status.Excluded, status.Included...)...) 59 } 60 61 func (setup *TestSetup) TempDir() error { 62 var err error 63 setup.tempDir, err = os.MkdirTemp("", "test") 64 65 return err 66 } 67 68 func (setup *TestSetup) TearDown() error { 69 return os.RemoveAll(setup.tempDir) 70 } 71 72 func (setup *TestSetup) Call() *Job { 73 setup.DownloaderParams.config = setup.Config 74 job := downloader( 75 context.Background(), 76 setup.MapFS, 77 setup.DownloaderParams, 78 ) 79 80 job.Start() 81 job.Wait() 82 return job 83 } 84 85 func (setup *TestSetup) RootDestination() string { 86 if setup.rootDestination != "" && setup.rootDestination[len(setup.rootDestination)-1:] == string(os.PathSeparator) { 87 return filepath.Join(setup.tempDir, setup.rootDestination) + string(os.PathSeparator) 88 } 89 90 return filepath.Join(setup.tempDir, setup.rootDestination) 91 } 92 93 func Test_downloadFolder_ending_in_slash(t *testing.T) { 94 setup := NewTestSetup() 95 setup.MapFS["some-path"] = &fstest.MapFile{ 96 Data: nil, 97 Mode: fs.ModeDir, 98 ModTime: time.Time{}, 99 Sys: files_sdk.File{DisplayName: "some-path", Path: "some-path", Type: "directory"}, 100 } 101 102 setup.MapFS["some-path/taco.png"] = &fstest.MapFile{ 103 Data: make([]byte, 100), 104 Mode: fs.ModePerm, 105 ModTime: time.Time{}, 106 Sys: files_sdk.File{DisplayName: "taco.png", Path: "some-path/taco.png", Type: "file", Size: 100}, 107 } 108 109 setup.DownloaderParams = DownloaderParams{RemotePath: "some-path", EventsReporter: setup.Reporter(), LocalPath: setup.RootDestination()} 110 setup.rootDestination = "some-path/" 111 setup.Call() 112 113 assert.Equal(t, 1, setup.reporterCalls[0].Job.Count()) 114 assert.Equal(t, 3, len(setup.reporterCalls)) 115 assert.Equal(t, status.Queued, setup.reporterCalls[0].Status) 116 assert.Equal(t, status.Downloading, setup.reporterCalls[1].Status) 117 assert.Equal(t, status.Complete, setup.reporterCalls[2].Status) 118 assert.NoError(t, setup.reporterCalls[2].err) 119 assert.Equal(t, "some-path/taco.png", setup.reporterCalls[0].File.Path) 120 assert.Equal(t, int64(0), setup.reporterCalls[0].TransferBytes) 121 122 assert.Equal(t, true, setup.reporterCalls[0].Job.All(status.Ended...)) 123 assert.Equal(t, int64(100), setup.reporterCalls[0].Job.TransferBytes()) 124 assert.Equal(t, int64(100), setup.reporterCalls[0].Job.TotalBytes()) 125 126 assert.NoError(t, setup.TearDown()) 127 } 128 129 func Test_downloader_RemoteStartingSlash(t *testing.T) { 130 setup := NewTestSetup() 131 setup.MapFS["some-path"] = &fstest.MapFile{ 132 Data: nil, 133 Mode: fs.ModeDir, 134 ModTime: time.Time{}, 135 Sys: files_sdk.File{DisplayName: "some-path", Path: "some-path", Type: "directory"}, 136 } 137 138 setup.MapFS["some-path/taco.png"] = &fstest.MapFile{ 139 Data: make([]byte, 100), 140 Mode: fs.ModePerm, 141 ModTime: time.Time{}, 142 Sys: files_sdk.File{DisplayName: "taco.png", Path: "some-path/taco.png", Type: "file", Size: 100}, 143 } 144 145 setup.DownloaderParams = DownloaderParams{RemotePath: "some-path", EventsReporter: setup.Reporter(), LocalPath: setup.RootDestination()} 146 setup.rootDestination = "some-path" + string(os.PathSeparator) 147 setup.Call() 148 149 fi, ok := setup.reporterCalls[0].Find(status.Errored) 150 if ok { 151 require.NoError(t, fi.Err()) 152 } 153 assert.Equal(t, 1, setup.reporterCalls[0].Job.Count()) 154 assert.Equal(t, 3, len(setup.reporterCalls)) 155 assert.Equal(t, status.Queued, setup.reporterCalls[0].Status) 156 assert.Equal(t, status.Downloading, setup.reporterCalls[1].Status) 157 assert.Equal(t, status.Complete, setup.reporterCalls[2].Status) 158 assert.NoError(t, setup.reporterCalls[2].err) 159 assert.Equal(t, "some-path/taco.png", setup.reporterCalls[0].File.Path) 160 assert.Equal(t, int64(0), setup.reporterCalls[0].TransferBytes) 161 162 assert.Equal(t, true, setup.reporterCalls[0].Job.All(status.Ended...)) 163 assert.Equal(t, int64(100), setup.reporterCalls[0].Job.TransferBytes()) 164 assert.Equal(t, int64(100), setup.reporterCalls[0].Job.TotalBytes()) 165 166 assert.NoError(t, setup.TearDown()) 167 } 168 169 func TestClient_Downloader(t *testing.T) { 170 t.Run("small file with size", func(t *testing.T) { 171 root := t.TempDir() 172 server := (&MockAPIServer{T: t}).Do() 173 defer server.Shutdown() 174 client := server.Client() 175 server.MockFiles["small-file-with-size.txt"] = mockFile{ 176 SizeTrust: TrustedSizeValue, 177 File: files_sdk.File{Size: 1999}, 178 } 179 job := client.Downloader(DownloaderParams{RemotePath: "small-file-with-size.txt", LocalPath: root + "/"}) 180 job.Start() 181 job.Wait() 182 assert.Len(t, job.Statuses, 1) 183 require.NoError(t, job.Statuses[0].Err()) 184 f, err := os.Open(filepath.Join(root, "small-file-with-size.txt")) 185 require.NoError(t, err) 186 stat, err := f.Stat() 187 require.NoError(t, err) 188 assert.Equal(t, int64(1999), stat.Size()) 189 }) 190 191 t.Run("large file with size", func(t *testing.T) { 192 root := t.TempDir() 193 server := (&MockAPIServer{T: t}).Do() 194 defer server.Shutdown() 195 client := server.Client() 196 server.MockFiles["large-file-with-size.txt"] = mockFile{ 197 SizeTrust: TrustedSizeValue, 198 File: files_sdk.File{Size: 19999999}, 199 } 200 job := client.Downloader(DownloaderParams{RemotePath: "large-file-with-size.txt", LocalPath: root + "/"}) 201 job.Start() 202 job.Wait() 203 assert.Len(t, job.Statuses, 1) 204 require.NoError(t, job.Statuses[0].Err()) 205 f, err := os.Open(filepath.Join(root, "large-file-with-size.txt")) 206 require.NoError(t, err) 207 stat, err := f.Stat() 208 require.NoError(t, err) 209 assert.Equal(t, int64(19999999), stat.Size()) 210 }) 211 212 t.Run("large file with size with max concurrent connections of 1", func(t *testing.T) { 213 root := t.TempDir() 214 server := (&MockAPIServer{T: t}).Do() 215 defer server.Shutdown() 216 client := server.Client() 217 server.MockFiles["large-file-with-size.txt"] = mockFile{ 218 SizeTrust: TrustedSizeValue, 219 File: files_sdk.File{Size: 1024 * 1024 * 100}, 220 MaxConnections: 1, 221 } 222 m := manager.Build(1, 1) 223 job := client.Downloader(DownloaderParams{RemotePath: "large-file-with-size.txt", LocalPath: root + "/", Manager: m}) 224 job.Start() 225 job.Wait() 226 assert.Len(t, job.Statuses, 1) 227 require.NoError(t, job.Statuses[0].Err()) 228 f, err := os.Open(filepath.Join(root, "large-file-with-size.txt")) 229 require.NoError(t, err) 230 stat, err := f.Stat() 231 require.NoError(t, err) 232 assert.Equal(t, int64(1024*1024*100), stat.Size()) 233 assert.Len(t, server.TrackRequest["/download/:download_id"], 1) 234 }) 235 236 t.Run("large file with size with max concurrent connections of 1", func(t *testing.T) { 237 root := t.TempDir() 238 server := (&MockAPIServer{T: t}).Do() 239 defer server.Shutdown() 240 client := server.Client() 241 server.MockFiles["large-file-with-size.txt"] = mockFile{ 242 SizeTrust: TrustedSizeValue, 243 File: files_sdk.File{Size: 1024 * 1024 * 50}, 244 MaxConnections: 1, 245 } 246 m := manager.Build(1, 1) 247 job := client.Downloader(DownloaderParams{RemotePath: "large-file-with-size.txt", LocalPath: root + "/", Manager: m}) 248 job.Start() 249 job.Wait() 250 assert.Len(t, job.Statuses, 1) 251 require.NoError(t, job.Statuses[0].Err()) 252 f, err := os.Open(filepath.Join(root, "large-file-with-size.txt")) 253 require.NoError(t, err) 254 stat, err := f.Stat() 255 require.NoError(t, err) 256 assert.Equal(t, int64(1024*1024*50), stat.Size()) 257 assert.Len(t, server.TrackRequest["/download/:download_id"], 1) 258 }) 259 260 t.Run("large file with size DownloadFilesAsSingleStream", func(t *testing.T) { 261 root := t.TempDir() 262 server := (&MockAPIServer{T: t}).Do() 263 defer server.Shutdown() 264 client := server.Client() 265 server.MockFiles["large-file-with-size.txt"] = mockFile{ 266 SizeTrust: TrustedSizeValue, 267 File: files_sdk.File{Size: 1024 * 1024 * 50}, 268 } 269 m := manager.Build(10, 1, true) 270 job := client.Downloader(DownloaderParams{RemotePath: "large-file-with-size.txt", LocalPath: root + "/", Manager: m}) 271 job.Start() 272 job.Wait() 273 assert.Len(t, job.Statuses, 1) 274 require.NoError(t, job.Statuses[0].Err()) 275 f, err := os.Open(filepath.Join(root, "large-file-with-size.txt")) 276 require.NoError(t, err) 277 stat, err := f.Stat() 278 require.NoError(t, err) 279 assert.Equal(t, int64(1024*1024*50), stat.Size()) 280 assert.Len(t, server.TrackRequest["/download/:download_id"], 1) 281 }) 282 283 t.Run("large file with no size", func(t *testing.T) { 284 root := t.TempDir() 285 server := (&MockAPIServer{T: t}).Do() 286 defer server.Shutdown() 287 client := server.Client() 288 server.MockFiles["large-file-with-no-size.txt"] = mockFile{ 289 SizeTrust: UntrustedSizeValue, 290 File: files_sdk.File{Size: 19999999}, 291 } 292 293 job := client.Downloader(DownloaderParams{RemotePath: "large-file-with-no-size.txt", LocalPath: root + "/"}) 294 job.Start() 295 job.Wait() 296 assert.Len(t, job.Statuses, 1) 297 require.NoError(t, job.Statuses[0].Err()) 298 f, err := os.Open(filepath.Join(root, "large-file-with-no-size.txt")) 299 require.NoError(t, err) 300 stat, err := f.Stat() 301 require.NoError(t, err) 302 assert.Equal(t, int64(19999999), stat.Size()) 303 }) 304 305 t.Run("large file with no size - extra parts are canceled", func(t *testing.T) { 306 root := t.TempDir() 307 server := (&MockAPIServer{T: t}).Do() 308 defer server.Shutdown() 309 client := server.Client() 310 realSize := int64((1024 * 1024 * 5) - 256) 311 server.MockFiles["large-file-with-no-size.txt"] = mockFile{ 312 SizeTrust: UntrustedSizeValue, 313 File: files_sdk.File{Size: 1024 * 1024 * 100}, 314 RealSize: &realSize, 315 } 316 317 job := client.Downloader(DownloaderParams{RemotePath: "large-file-with-no-size.txt", LocalPath: root + "/"}) 318 job.Start() 319 job.Wait() 320 assert.Len(t, job.Statuses, 1) 321 require.NoError(t, job.Statuses[0].Err()) 322 f, err := os.Open(filepath.Join(root, "large-file-with-no-size.txt")) 323 require.NoError(t, err) 324 stat, err := f.Stat() 325 require.NoError(t, err) 326 assert.Equal(t, realSize, stat.Size()) 327 }) 328 329 t.Run("large file with no size - client does not receive all bytes server reported to send", func(t *testing.T) { 330 root := t.TempDir() 331 server := (&MockAPIServer{T: t}).Do() 332 defer server.Shutdown() 333 client := server.Client() 334 serverBytesSent := int64((1024 * 1024 * 5) + 256) 335 server.MockFiles["large-file-with-no-size.txt"] = mockFile{ 336 SizeTrust: UntrustedSizeValue, 337 File: files_sdk.File{Size: 1024 * 1024 * 15}, 338 ServerBytesSent: &serverBytesSent, 339 } 340 341 job := client.Downloader(DownloaderParams{RemotePath: "large-file-with-no-size.txt", LocalPath: root + "/"}) 342 job.Start() 343 job.Wait() 344 assert.Len(t, job.Statuses, 1) 345 require.EqualError(t, job.Statuses[0].Err(), `received size did not match server send size 346 expected 5243136 bytes sent 5242880 received`) 347 _, err := os.Open(filepath.Join(root, "large-file-with-no-size.txt")) 348 require.Error(t, err) 349 }) 350 351 t.Run("large file with no size - client received more bytes than server reported to send", func(t *testing.T) { 352 root := t.TempDir() 353 server := (&MockAPIServer{T: t}).Do() 354 defer server.Shutdown() 355 client := server.Client() 356 serverBytesSent := int64(1024 * 1024 * 4) 357 server.MockFiles["large-file-with-no-size.txt"] = mockFile{ 358 SizeTrust: UntrustedSizeValue, 359 File: files_sdk.File{Size: 1024 * 1024 * 15}, 360 ServerBytesSent: &serverBytesSent, 361 } 362 363 job := client.Downloader(DownloaderParams{RemotePath: "large-file-with-no-size.txt", LocalPath: root + "/"}) 364 job.Start() 365 job.Wait() 366 assert.Len(t, job.Statuses, 1) 367 require.EqualError(t, job.Statuses[0].Err(), `received size did not match server send size 368 expected 4194304 bytes sent 5242880 received`) 369 _, err := os.Open(filepath.Join(root, "large-file-with-no-size.txt")) 370 require.Error(t, err) 371 }) 372 373 t.Run("large file with no size - when sever has invalid request status", func(t *testing.T) { 374 root := t.TempDir() 375 server := (&MockAPIServer{T: t}).Do() 376 defer server.Shutdown() 377 client := server.Client() 378 serverBytesSent := int64(1024 * 1024 * 4) 379 server.MockFiles["large-file-with-no-size.txt"] = mockFile{ 380 SizeTrust: UntrustedSizeValue, 381 File: files_sdk.File{Size: 1024 * 1024 * 15}, 382 ServerBytesSent: &serverBytesSent, 383 ForceRequestStatus: "started", 384 } 385 386 job := client.Downloader(DownloaderParams{RemotePath: "large-file-with-no-size.txt", LocalPath: root + "/"}) 387 job.Start() 388 job.Wait() 389 assert.Len(t, job.Statuses, 1) 390 require.NoError(t, job.Statuses[0].Err()) 391 f, err := os.Open(filepath.Join(root, "large-file-with-no-size.txt")) 392 require.NoError(t, err) 393 stat, err := f.Stat() 394 require.NoError(t, err) 395 assert.Equal(t, int64(1024*1024*15), stat.Size()) 396 }) 397 398 t.Run("large file with no size - when sever has failed request status", func(t *testing.T) { 399 root := t.TempDir() 400 server := (&MockAPIServer{T: t}).Do() 401 defer server.Shutdown() 402 client := server.Client() 403 server.MockFiles["large-file-with-no-size.txt"] = mockFile{ 404 SizeTrust: UntrustedSizeValue, 405 File: files_sdk.File{Size: 1024 * 1024 * 15}, 406 ForceRequestStatus: "failed", 407 ForceRequestMessage: "problem", 408 } 409 var events []JobFile 410 eventReporter := CreateFileEvents( 411 func(file JobFile) { 412 events = append(events, file) 413 }, 414 status.Included..., 415 ) 416 417 job := client.Downloader(DownloaderParams{RemotePath: "large-file-with-no-size.txt", LocalPath: root + "/", EventsReporter: eventReporter}) 418 transferBytes := []string{"zero"} 419 wait := make(chan bool) 420 go func() { 421 for { 422 select { 423 case <-job.Finished.C: 424 wait <- true 425 return 426 default: 427 bytes := job.TransferBytes() 428 if bytes > 0 && transferBytes[len(transferBytes)-1] == "zero" { 429 transferBytes = append(transferBytes, "bytes") 430 } 431 if bytes == 0 && transferBytes[len(transferBytes)-1] != "zero" { 432 transferBytes = append(transferBytes, "zero") 433 } 434 } 435 } 436 }() 437 438 job.Start() 439 job.Wait() 440 assert.Len(t, job.Statuses, 1) 441 assert.Error(t, job.Statuses[0].Err(), `received size did not match server send size 442 expected 4194304 bytes sent 5242880 received`) 443 assert.Equal(t, []int64{0, 32768, 0}, lo.Map[JobFile, int64](events, func(item JobFile, index int) int64 { return item.TransferBytes })) 444 assert.Equal(t, []string{"queued", "downloading", "errored"}, lo.Map[JobFile, string](events, func(item JobFile, index int) string { return item.StatusName })) 445 <-wait 446 assert.GreaterOrEqual(t, lo.Count[string](transferBytes, "zero"), 2, "After error transfer bytes are set to zero") 447 assert.GreaterOrEqual(t, lo.Count[string](transferBytes, "bytes"), 2, "After error transfer bytes are set to zero") 448 }) 449 450 t.Run("large file with bad size info real size is bigger", func(t *testing.T) { 451 root := t.TempDir() 452 server := (&MockAPIServer{T: t}).Do() 453 defer server.Shutdown() 454 client := server.Client() 455 realSize := int64(20000000) 456 server.MockFiles["file-with-mismatch-size-bigger"] = mockFile{ 457 SizeTrust: UntrustedSizeValue, 458 File: files_sdk.File{Size: 19999999}, 459 RealSize: &realSize, 460 } 461 462 job := client.Downloader(DownloaderParams{RemotePath: "file-with-mismatch-size-bigger", LocalPath: root + "/"}) 463 job.Start() 464 job.Wait() 465 require.Len(t, job.Statuses, 1) 466 require.NoError(t, job.Statuses[0].Err()) 467 f, err := os.Open(filepath.Join(root, "file-with-mismatch-size-bigger")) 468 require.NoError(t, err) 469 stat, err := f.Stat() 470 require.NoError(t, err) 471 assert.Equal(t, int64(20000000), stat.Size()) 472 }) 473 474 t.Run("large file with bad size info real size is smaller", func(t *testing.T) { 475 root := t.TempDir() 476 server := (&MockAPIServer{T: t}).Do() 477 defer server.Shutdown() 478 client := server.Client() 479 realSize := int64(19999999) 480 server.MockFiles["file-with-mismatch-size-smaller"] = mockFile{ 481 SizeTrust: UntrustedSizeValue, 482 File: files_sdk.File{Size: 20000000}, 483 RealSize: &realSize, 484 } 485 486 job := client.Downloader(DownloaderParams{RemotePath: "file-with-mismatch-size-smaller", LocalPath: root + "/"}) 487 job.Start() 488 job.Wait() 489 require.Len(t, job.Statuses, 1) 490 require.NoError(t, job.Statuses[0].Err()) 491 f, err := os.Open(filepath.Join(root, "file-with-mismatch-size-smaller")) 492 require.NoError(t, err) 493 stat, err := f.Stat() 494 require.NoError(t, err) 495 assert.Equal(t, int64(19999999), stat.Size()) 496 }) 497 498 multipleFiles := func(relativeRoot string, t *testing.T) { 499 root := t.TempDir() 500 server := (&MockAPIServer{T: t}).Do() 501 defer server.Shutdown() 502 client := server.Client() 503 server.MockFiles[filepath.Join(relativeRoot, "file1")] = mockFile{ 504 SizeTrust: TrustedSizeValue, 505 File: files_sdk.File{Size: 6}, 506 } 507 server.MockFiles[filepath.Join(relativeRoot, "file2")] = mockFile{ 508 SizeTrust: TrustedSizeValue, 509 File: files_sdk.File{Size: 1024 * 1024}, 510 } 511 server.MockFiles[filepath.Join(relativeRoot, "file3")] = mockFile{ 512 SizeTrust: TrustedSizeValue, 513 File: files_sdk.File{Size: 1024 * 1024 * 2}, 514 } 515 server.MockFiles[filepath.Join(relativeRoot, "file4")] = mockFile{ 516 SizeTrust: TrustedSizeValue, 517 File: files_sdk.File{Size: 1024 * 1024 * 10}, 518 } 519 server.MockFiles[filepath.Join(relativeRoot, "file5")] = mockFile{ 520 SizeTrust: TrustedSizeValue, 521 File: files_sdk.File{Size: 100}, 522 } 523 if relativeRoot != "" { 524 server.MockFiles[relativeRoot] = mockFile{ 525 File: files_sdk.File{Type: "directory"}, 526 } 527 } 528 529 job := client.Downloader(DownloaderParams{RemotePath: relativeRoot, LocalPath: root + "/"}) 530 job.Start() 531 job.Wait() 532 assert.Len(t, job.Statuses, 5) 533 require.NoError(t, job.Statuses[0].Err()) 534 535 for k, v := range server.MockFiles { 536 f, err := os.Open(filepath.Join(root, k)) 537 require.NoError(t, err) 538 stat, err := f.Stat() 539 require.NoError(t, err) 540 if !stat.IsDir() { 541 assert.Equal(t, v.Size, stat.Size()) 542 } 543 } 544 } 545 546 t.Run("list folder from a path", func(t *testing.T) { 547 multipleFiles("a-root", t) 548 }) 549 550 t.Run("multiple files from root", func(t *testing.T) { 551 multipleFiles("", t) 552 }) 553 554 t.Run("PreserveTimes with mtime", func(t *testing.T) { 555 root := t.TempDir() 556 server := (&MockAPIServer{T: t}).Do() 557 defer server.Shutdown() 558 client := server.Client() 559 mtime := time.Date(2010, 11, 17, 20, 34, 58, 651387237, time.UTC).Truncate(time.Millisecond) 560 server.MockFiles["small-file-with-size.txt"] = mockFile{ 561 SizeTrust: TrustedSizeValue, 562 File: files_sdk.File{Size: 1999, Mtime: &mtime}, 563 } 564 job := client.Downloader(DownloaderParams{RemotePath: "small-file-with-size.txt", LocalPath: root + "/", PreserveTimes: true}) 565 job.Start() 566 job.Wait() 567 assert.Len(t, job.Statuses, 1) 568 require.NoError(t, job.Statuses[0].Err()) 569 f, err := os.Open(filepath.Join(root, "small-file-with-size.txt")) 570 require.NoError(t, err) 571 stat, err := f.Stat() 572 require.NoError(t, err) 573 assert.Equal(t, int64(1999), stat.Size()) 574 assert.Equal(t, mtime, stat.ModTime().UTC()) 575 }) 576 577 t.Run("PreserveTimes with providedMtime", func(t *testing.T) { 578 root := t.TempDir() 579 server := (&MockAPIServer{T: t}).Do() 580 defer server.Shutdown() 581 client := server.Client() 582 providedMtime := time.Date(2010, 11, 17, 20, 34, 58, 651387237, time.UTC).Truncate(time.Millisecond) 583 server.MockFiles["small-file-with-size.txt"] = mockFile{ 584 SizeTrust: TrustedSizeValue, 585 File: files_sdk.File{Size: 1999, Mtime: lib.Time(time.Now()), ProvidedMtime: &providedMtime}, 586 } 587 job := client.Downloader(DownloaderParams{RemotePath: "small-file-with-size.txt", LocalPath: root + "/", PreserveTimes: true}) 588 job.Start() 589 job.Wait() 590 assert.Len(t, job.Statuses, 1) 591 require.NoError(t, job.Statuses[0].Err()) 592 f, err := os.Open(filepath.Join(root, "small-file-with-size.txt")) 593 require.NoError(t, err) 594 stat, err := f.Stat() 595 require.NoError(t, err) 596 assert.Equal(t, int64(1999), stat.Size()) 597 assert.Equal(t, providedMtime, stat.ModTime().UTC()) 598 }) 599 600 t.Run("sync already downloaded", func(t *testing.T) { 601 root := t.TempDir() 602 server := (&MockAPIServer{T: t}).Do() 603 defer server.Shutdown() 604 client := server.Client() 605 server.MockFiles["taco.png"] = mockFile{ 606 SizeTrust: TrustedSizeValue, 607 File: files_sdk.File{Size: 100}, 608 } 609 taco, err := os.Create(filepath.Join(root, "taco.png")) 610 assert.NoError(t, err) 611 _, err = taco.Write(make([]byte, 100)) 612 require.NoError(t, err) 613 require.NoError(t, taco.Close()) 614 job := client.Downloader(DownloaderParams{Sync: true, RemotePath: "taco.png", LocalPath: root + "/"}) 615 job.Start() 616 job.Wait() 617 assert.Len(t, job.Statuses, 1) 618 require.NoError(t, job.Statuses[0].Err()) 619 assert.Equal(t, status.Skipped, job.Statuses[0].Status()) 620 }) 621 622 t.Run("sync does not exist locally", func(t *testing.T) { 623 root := t.TempDir() 624 server := (&MockAPIServer{T: t}).Do() 625 defer server.Shutdown() 626 client := server.Client() 627 server.MockFiles["taco.png"] = mockFile{ 628 SizeTrust: TrustedSizeValue, 629 File: files_sdk.File{Size: 100}, 630 } 631 job := client.Downloader(DownloaderParams{Sync: true, RemotePath: "taco.png", LocalPath: root + "/"}) 632 job.Start() 633 job.Wait() 634 assert.Len(t, job.Statuses, 1) 635 require.NoError(t, job.Statuses[0].Err()) 636 assert.Equal(t, status.Complete, job.Statuses[0].Status()) 637 }) 638 639 t.Run("sync is out of date locally by size", func(t *testing.T) { 640 root := t.TempDir() 641 server := (&MockAPIServer{T: t}).Do() 642 defer server.Shutdown() 643 client := server.Client() 644 server.MockFiles["taco.png"] = mockFile{ 645 SizeTrust: TrustedSizeValue, 646 File: files_sdk.File{Size: 100}, 647 } 648 taco, err := os.Create(filepath.Join(root, "taco.png")) 649 assert.NoError(t, err) 650 require.NoError(t, taco.Close()) 651 job := client.Downloader(DownloaderParams{Sync: true, RemotePath: "taco.png", LocalPath: root + "/"}) 652 job.Start() 653 job.Wait() 654 assert.Len(t, job.Statuses, 1) 655 require.NoError(t, job.Statuses[0].Err()) 656 assert.Equal(t, status.Complete, job.Statuses[0].Status()) 657 }) 658 659 t.Run("local directory is privileged", func(t *testing.T) { 660 root := t.TempDir() 661 server := (&MockAPIServer{T: t}).Do() 662 defer server.Shutdown() 663 client := server.Client() 664 server.MockFiles["taco.png"] = mockFile{ 665 SizeTrust: TrustedSizeValue, 666 File: files_sdk.File{Size: 100}, 667 } 668 669 require.NoError(t, os.Mkdir(filepath.Join(root, "restricted"), 0000)) 670 671 t.Cleanup(func() { 672 require.NoError(t, os.Chmod(filepath.Join(root, "restricted"), 0777)) 673 }) 674 675 job := client.Downloader(DownloaderParams{Sync: true, RemotePath: "taco.png", LocalPath: filepath.Join(root, "restricted") + string(os.PathSeparator)}) 676 job.Start() 677 job.Wait() 678 assert.Len(t, job.Statuses, 1) 679 require.True(t, os.IsPermission(job.Statuses[0].Err())) 680 assert.Equal(t, status.Errored, job.Statuses[0].Status()) 681 }) 682 683 t.Run("local path is invalid", func(t *testing.T) { 684 server := (&MockAPIServer{T: t}).Do() 685 defer server.Shutdown() 686 client := server.Client() 687 server.MockFiles["taco.png"] = mockFile{ 688 SizeTrust: TrustedSizeValue, 689 File: files_sdk.File{Size: 100}, 690 } 691 692 job := client.Downloader(DownloaderParams{Sync: true, RemotePath: "taco.png", LocalPath: "invalid\000path"}) 693 job.Start() 694 job.Wait() 695 assert.Len(t, job.Statuses, 1) 696 require.Error(t, job.Statuses[0].Err()) 697 require.Contains(t, job.Statuses[0].Err().Error(), "invalid argument") 698 assert.Equal(t, status.Errored, job.Statuses[0].Status()) 699 }) 700 } 701 702 func TestDownload(t *testing.T) { 703 mutex := &sync.Mutex{} 704 t.Run("downloader", func(t *testing.T) { 705 sourceFs := &FS{Context: context.Background()} 706 destinationFs := lib.ReadWriteFs(lib.LocalFileSystem{}) 707 for _, tt := range lib.PathSpec(sourceFs.PathSeparator(), destinationFs.PathSeparator()) { 708 t.Run(tt.Name, func(t *testing.T) { 709 client, r, err := CreateClient(t.Name()) 710 if err != nil { 711 t.Fatal(err) 712 } 713 config := client.Config 714 sourceFs := (&FS{Context: context.Background()}).Init(config, false) 715 lib.BuildPathSpecTest(t, mutex, tt, sourceFs, destinationFs, func(args lib.PathSpecArgs) lib.Cmd { 716 return &CmdRunner{ 717 run: func() *Job { 718 return downloader(context.Background(), sourceFs, DownloaderParams{config: config, RemotePath: args.Src, LocalPath: args.Dest, PreserveTimes: args.PreserveTimes}) 719 }, 720 args: []string{args.Src, args.Dest, "--times", fmt.Sprintf("%v", args.PreserveTimes)}, 721 } 722 }) 723 r.Stop() 724 }) 725 } 726 }) 727 } 728 729 type CmdRunner struct { 730 run func() *Job 731 stderr io.Writer 732 stdout io.Writer 733 args []string 734 *Job 735 } 736 737 func (c *CmdRunner) Run() error { 738 c.Job = c.run() 739 c.Job.Start() 740 c.Job.Wait() 741 for _, f := range c.Job.Sub(status.Errored).Statuses { 742 c.stderr.Write([]byte(f.Err().Error())) 743 } 744 return nil 745 } 746 747 func (c *CmdRunner) Args() []string { 748 return c.args 749 } 750 751 func (c *CmdRunner) SetOut(w io.Writer) { 752 c.stdout = w 753 } 754 755 func (c *CmdRunner) SetErr(stderr io.Writer) { 756 c.stderr = stderr 757 }