github.com/release-engineering/exodus-rsync@v1.11.2/internal/cmd/cmd_sync_test.go (about) 1 package cmd 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "path" 8 "reflect" 9 "testing" 10 11 "github.com/golang/mock/gomock" 12 "github.com/release-engineering/exodus-rsync/internal/conf" 13 "github.com/release-engineering/exodus-rsync/internal/gw" 14 "github.com/release-engineering/exodus-rsync/internal/walk" 15 ) 16 17 const CONFIG string = ` 18 environments: 19 - prefix: exodus 20 gwenv: best-env 21 22 - prefix: exodus-mixed 23 gwenv: best-env 24 rsyncmode: mixed 25 26 - prefix: somehost:/cdn/root 27 gwenv: best-env 28 rsyncmode: exodus 29 30 - prefix: otherhost:/foo/bar/baz 31 gwenv: best-env 32 rsyncmode: exodus 33 strip: otherhost:/foo 34 ` 35 36 type EnvMatcher struct { 37 name string 38 } 39 40 func (m EnvMatcher) Matches(x interface{}) bool { 41 env, ok := x.(conf.EnvironmentConfig) 42 if !ok { 43 return false 44 } 45 return env.GwEnv() == m.name 46 } 47 48 func (m EnvMatcher) String() string { 49 return fmt.Sprintf("Environment '%s'", m.name) 50 } 51 52 type FakeClient struct { 53 blobs map[string]string 54 publishes []FakePublish 55 } 56 57 type FakePublish struct { 58 items []gw.ItemInput 59 committed int 60 commitmodes []string 61 frozen bool 62 id string 63 } 64 65 type BrokenPublish struct { 66 id string 67 } 68 69 func (c *FakeClient) EnsureUploaded(ctx context.Context, items []walk.SyncItem, 70 onUploaded func(walk.SyncItem) error, 71 onExisting func(walk.SyncItem) error, 72 onDuplicate func(walk.SyncItem) error, 73 ) error { 74 var err error 75 processedItems := make(map[string]walk.SyncItem) 76 77 for _, item := range items { 78 if _, ok := processedItems[item.Key]; ok { 79 err = onDuplicate(item) 80 } else if _, ok := c.blobs[item.Key]; ok { 81 err = onExisting(item) 82 } else { 83 c.blobs[item.Key] = item.SrcPath 84 processedItems[item.Key] = item 85 err = onUploaded(item) 86 } 87 if err != nil { 88 return err 89 } 90 } 91 return nil 92 } 93 94 func (c *FakeClient) NewPublish(ctx context.Context) (gw.Publish, error) { 95 c.publishes = append(c.publishes, FakePublish{id: "3e0a4539-be4a-437e-a45f-6d72f7192f17"}) 96 return &c.publishes[len(c.publishes)-1], nil 97 } 98 99 func (c *FakeClient) GetPublish(ctx context.Context, id string) (gw.Publish, error) { 100 for idx := range c.publishes { 101 if c.publishes[idx].id == id { 102 return &c.publishes[idx], nil 103 } 104 } 105 // Didn't find any, then return nil 106 return nil, fmt.Errorf("publish not found: '%s'", id) 107 } 108 109 func (c *FakeClient) WhoAmI(context.Context) (map[string]interface{}, error) { 110 out := make(map[string]interface{}) 111 out["whoami"] = "fake-info" 112 return out, nil 113 } 114 115 func (p *FakePublish) AddItems(ctx context.Context, items []gw.ItemInput) error { 116 if p.frozen { 117 return fmt.Errorf("attempted to modify committed publish") 118 } 119 p.items = append(p.items, items...) 120 return nil 121 } 122 123 func (p *BrokenPublish) AddItems(_ context.Context, _ []gw.ItemInput) error { 124 return fmt.Errorf("invalid publish") 125 } 126 127 func (p *BrokenPublish) Commit(_ context.Context, _ string) error { 128 return fmt.Errorf("invalid publish") 129 } 130 131 func (p *FakePublish) Commit(ctx context.Context, mode string) error { 132 if mode == "" || mode == "phase2" { 133 p.frozen = true 134 } 135 p.committed++ 136 p.commitmodes = append(p.commitmodes, mode) 137 return nil 138 } 139 140 func (p *FakePublish) ID() string { 141 return p.id 142 } 143 144 func (p *BrokenPublish) ID() string { 145 return p.id 146 } 147 148 func TestMainTypicalSync(t *testing.T) { 149 wd, err := os.Getwd() 150 if err != nil { 151 t.Fatal(err) 152 } 153 154 SetConfig(t, CONFIG) 155 ctrl := MockController(t) 156 157 tests := []struct { 158 name string 159 commitArg string 160 expectedCommits int 161 expectedCommitMode string 162 }{ 163 {"typical", "", 1, ""}, 164 165 {"explicit autocommit", "--exodus-commit=auto", 1, ""}, 166 167 {"no commit", "--exodus-commit=none", 0, ""}, 168 169 {"commit specified mode", "--exodus-commit=xyz", 1, "xyz"}, 170 } 171 172 for _, tt := range tests { 173 t.Run(tt.name, func(t *testing.T) { 174 mockGw := gw.NewMockInterface(ctrl) 175 ext.gw = mockGw 176 177 client := FakeClient{blobs: make(map[string]string)} 178 mockGw.EXPECT().NewClient(gomock.Any(), EnvMatcher{"best-env"}).Return(&client, nil) 179 180 srcPath := path.Clean(wd + "/../../test/data/srctrees/just-files") 181 182 args := []string{ 183 "rsync", 184 srcPath + "/", 185 } 186 187 if tt.commitArg != "" { 188 args = append(args, tt.commitArg) 189 } 190 args = append(args, "exodus:/some/target") 191 192 got := Main(args) 193 194 // It should complete successfully. 195 if got != 0 { 196 t.Error("returned incorrect exit code", got) 197 } 198 199 // Check paths of some blobs we expected to deal with. 200 binPath := client.blobs["c66f610d98b2c9fe0175a3e99ba64d7fc7de45046515ff325be56329a9347dd6"] 201 helloPath := client.blobs["5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03"] 202 203 // It should have uploaded the binary from here 204 if binPath != srcPath+"/subdir/some-binary" { 205 t.Error("binary uploaded from unexpected path", binPath) 206 } 207 208 // For the hello file, since there were two copies, it's undefined which one of them 209 // was used for the upload - but should be one of them. 210 if helloPath != srcPath+"/hello-copy-one" && helloPath != srcPath+"/hello-copy-two" { 211 t.Error("hello uploaded from unexpected path", helloPath) 212 } 213 214 // It should have created one publish. 215 if len(client.publishes) != 1 { 216 t.Error("expected to create 1 publish, instead created", len(client.publishes)) 217 } 218 219 p := client.publishes[0] 220 221 // Build up a URI => Key mapping of what was published 222 itemMap := make(map[string]string) 223 for _, item := range p.items { 224 if _, ok := itemMap[item.WebURI]; ok { 225 t.Error("tried to publish this URI more than once:", item.WebURI) 226 } 227 itemMap[item.WebURI] = item.ObjectKey 228 } 229 230 // It should have been exactly this 231 expectedItems := map[string]string{ 232 "/some/target/subdir/some-binary": "c66f610d98b2c9fe0175a3e99ba64d7fc7de45046515ff325be56329a9347dd6", 233 "/some/target/hello-copy-one": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", 234 "/some/target/hello-copy-two": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", 235 } 236 237 if !reflect.DeepEqual(itemMap, expectedItems) { 238 t.Error("did not publish expected items, published:", itemMap) 239 } 240 241 // It should have committed (or not) as expected 242 if p.committed != tt.expectedCommits { 243 t.Error("expected ", tt.expectedCommits, " commits, got ", p.committed) 244 } 245 246 // If a commit happened at all, it should have used the mode we expect. 247 if tt.expectedCommits != 0 { 248 if p.commitmodes[0] != tt.expectedCommitMode { 249 t.Error("expected ", tt.expectedCommitMode, " commit mode, got ", p.commitmodes[0]) 250 } 251 } 252 }) 253 } 254 } 255 256 func TestMainSyncFilter(t *testing.T) { 257 wd, err := os.Getwd() 258 if err != nil { 259 t.Fatal(err) 260 } 261 262 SetConfig(t, CONFIG) 263 ctrl := MockController(t) 264 265 mockGw := gw.NewMockInterface(ctrl) 266 ext.gw = mockGw 267 268 client := FakeClient{blobs: make(map[string]string)} 269 mockGw.EXPECT().NewClient(gomock.Any(), EnvMatcher{"best-env"}).Return(&client, nil) 270 271 srcPath := path.Clean(wd + "/../../test/data/srctrees") 272 273 args := []string{ 274 "rsync", 275 "--filter", "+ */", 276 "--filter", "+/ **/hello-copy*", 277 "--filter", "- *", 278 srcPath + "/", 279 "exodus:/some/target", 280 } 281 282 got := Main(args) 283 284 // It should complete successfully. 285 if got != 0 { 286 t.Error("returned incorrect exit code", got) 287 } 288 289 // Check paths of some blobs we expected to deal with. 290 helloPath := client.blobs["5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03"] 291 292 // For the hello file, since there were two copies, it's undefined which one of them 293 // was used for the upload - but should be one of them. 294 if helloPath != srcPath+"/just-files/hello-copy-one" && helloPath != srcPath+"/just-files/hello-copy-two" { 295 t.Error("hello uploaded from unexpected path", helloPath) 296 } 297 298 // It should have created one publish. 299 if len(client.publishes) != 1 { 300 t.Error("expected to create 1 publish, instead created", len(client.publishes)) 301 } 302 303 p := client.publishes[0] 304 305 // Build up a URI => Key mapping of what was published 306 itemMap := make(map[string]string) 307 for _, item := range p.items { 308 if _, ok := itemMap[item.WebURI]; ok { 309 t.Error("tried to publish this URI more than once:", item.WebURI) 310 } 311 itemMap[item.WebURI] = item.ObjectKey 312 } 313 314 // It should have been exactly this 315 expectedItems := map[string]string{ 316 "/some/target/just-files/hello-copy-one": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", 317 "/some/target/just-files/hello-copy-two": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", 318 } 319 320 if !reflect.DeepEqual(itemMap, expectedItems) { 321 t.Error("did not publish expected items, published:", itemMap) 322 } 323 324 // It should have committed the publish (once) 325 if p.committed != 1 { 326 t.Error("expected to commit publish (once), instead p.committed ==", p.committed) 327 } 328 } 329 330 func TestMainSyncFilterIsRelative(t *testing.T) { 331 wd, err := os.Getwd() 332 if err != nil { 333 t.Fatal(err) 334 } 335 336 SetConfig(t, CONFIG) 337 ctrl := MockController(t) 338 339 mockGw := gw.NewMockInterface(ctrl) 340 ext.gw = mockGw 341 342 client := FakeClient{blobs: make(map[string]string)} 343 mockGw.EXPECT().NewClient(gomock.Any(), EnvMatcher{"best-env"}).Return(&client, nil) 344 345 srcPath := path.Clean(wd + "/../../test/data/srctrees/just-files") 346 347 // Nothing should match --exclude, as filtered paths are relative. 348 args := []string{ 349 "rsync", 350 "--exclude", path.Clean(wd), 351 srcPath + "/", 352 "exodus:/some/target", 353 } 354 355 got := Main(args) 356 357 // It should complete successfully. 358 if got != 0 { 359 t.Error("returned incorrect exit code", got) 360 } 361 362 // Check paths of some blobs we expected to deal with. 363 helloPath := client.blobs["5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03"] 364 365 // For the hello file, since there were two copies, it's undefined which one of them 366 // was used for the upload - but should be one of them. 367 if helloPath != srcPath+"/hello-copy-one" && helloPath != srcPath+"/hello-copy-two" { 368 t.Error("hello uploaded from unexpected path", helloPath) 369 } 370 371 // It should have created one publish. 372 if len(client.publishes) != 1 { 373 t.Error("expected to create 1 publish, instead created", len(client.publishes)) 374 } 375 376 p := client.publishes[0] 377 378 // Build up a URI => Key mapping of what was published 379 itemMap := make(map[string]string) 380 for _, item := range p.items { 381 if _, ok := itemMap[item.WebURI]; ok { 382 t.Error("tried to publish this URI more than once:", item.WebURI) 383 } 384 itemMap[item.WebURI] = item.ObjectKey 385 } 386 387 // It should have been exactly this 388 expectedItems := map[string]string{ 389 "/some/target/hello-copy-one": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", 390 "/some/target/hello-copy-two": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", 391 "/some/target/subdir/some-binary": "c66f610d98b2c9fe0175a3e99ba64d7fc7de45046515ff325be56329a9347dd6", 392 } 393 394 if !reflect.DeepEqual(itemMap, expectedItems) { 395 t.Error("did not publish expected items, published:", itemMap) 396 } 397 398 // It should have committed the publish (once) 399 if p.committed != 1 { 400 t.Error("expected to commit publish (once), instead p.committed ==", p.committed) 401 } 402 } 403 404 func TestMainSyncFollowsLinks(t *testing.T) { 405 wd, err := os.Getwd() 406 if err != nil { 407 t.Fatal(err) 408 } 409 410 SetConfig(t, CONFIG) 411 ctrl := MockController(t) 412 413 mockGw := gw.NewMockInterface(ctrl) 414 ext.gw = mockGw 415 416 client := FakeClient{blobs: make(map[string]string)} 417 mockGw.EXPECT().NewClient(gomock.Any(), EnvMatcher{"best-env"}).Return(&client, nil) 418 419 srcPath := path.Clean(wd + "/../../test/data/srctrees/links") 420 421 args := []string{ 422 "rsync", 423 "-vvv", 424 srcPath + "/", 425 "exodus:/dest", 426 } 427 428 got := Main(args) 429 430 // It should complete successfully. 431 if got != 0 { 432 t.Error("returned incorrect exit code", got) 433 } 434 435 // It should have created one publish. 436 if len(client.publishes) != 1 { 437 t.Error("expected to create 1 publish, instead created", len(client.publishes)) 438 } 439 440 p := client.publishes[0] 441 442 // Build up a URI => Key mapping of what was published 443 itemMap := make(map[string]string) 444 for _, item := range p.items { 445 if _, ok := itemMap[item.WebURI]; ok { 446 t.Error("tried to publish this URI more than once:", item.WebURI) 447 } 448 itemMap[item.WebURI] = item.ObjectKey 449 } 450 451 // It should have been exactly this 452 expectedItems := map[string]string{ 453 "/dest/link-to-regular-file": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", 454 "/dest/subdir/regular-file": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", 455 "/dest/subdir/rand1": "57921e8a0929eaff5003cc9dd528c3421296055a4de2ba72429dc7f41bfa8411", 456 "/dest/subdir/rand2": "f3a5340ae2a400803b8150f455ad285d173cbdcf62c8e9a214b30f467f45b310", 457 "/dest/subdir2/dir-link/regular-file": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", 458 "/dest/subdir2/dir-link/rand1": "57921e8a0929eaff5003cc9dd528c3421296055a4de2ba72429dc7f41bfa8411", 459 "/dest/subdir2/dir-link/rand2": "f3a5340ae2a400803b8150f455ad285d173cbdcf62c8e9a214b30f467f45b310", 460 "/dest/some/somefile": "57921e8a0929eaff5003cc9dd528c3421296055a4de2ba72429dc7f41bfa8411", 461 "/dest/some/dir/link-to-somefile": "57921e8a0929eaff5003cc9dd528c3421296055a4de2ba72429dc7f41bfa8411", 462 } 463 464 if !reflect.DeepEqual(itemMap, expectedItems) { 465 t.Error("did not publish expected items, published:", itemMap) 466 } 467 468 // It should have committed the publish (once) 469 if p.committed != 1 { 470 t.Error("expected to commit publish (once), instead p.committed ==", p.committed) 471 } 472 } 473 474 func TestMainSyncDontFollowLinks(t *testing.T) { 475 wd, err := os.Getwd() 476 if err != nil { 477 t.Fatal(err) 478 } 479 480 SetConfig(t, CONFIG) 481 ctrl := MockController(t) 482 483 mockGw := gw.NewMockInterface(ctrl) 484 ext.gw = mockGw 485 486 client := FakeClient{blobs: make(map[string]string)} 487 mockGw.EXPECT().NewClient(gomock.Any(), EnvMatcher{"best-env"}).Return(&client, nil) 488 489 srcPath := path.Clean(wd + "/../../test/data/srctrees/links") 490 491 args := []string{ 492 "rsync", 493 "-lvvv", 494 srcPath + "/", 495 "somehost:/cdn/root/some/target", 496 } 497 498 got := Main(args) 499 500 // It should complete successfully. 501 if got != 0 { 502 t.Error("returned incorrect exit code", got) 503 } 504 505 // Check paths of some blobs we expected to deal with. 506 binPath := client.blobs["5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03"] 507 508 // It should have uploaded the binary from here 509 if binPath != srcPath+"/subdir/regular-file" { 510 t.Error("binary uploaded from unexpected path", binPath) 511 } 512 513 // It should have created one publish. 514 if len(client.publishes) != 1 { 515 t.Error("expected to create 1 publish, instead created", len(client.publishes)) 516 } 517 518 p := client.publishes[0] 519 520 // Build up a URI => Key mapping of what was published 521 itemMap := make(map[string]string) 522 for _, item := range p.items { 523 if _, ok := itemMap[item.WebURI]; ok { 524 t.Error("tried to publish this URI more than once:", item.WebURI) 525 } 526 527 if item.LinkTo != "" { 528 itemMap[item.WebURI] = "link-" + item.LinkTo 529 } else if item.ObjectKey != "" { 530 itemMap[item.WebURI] = "key-" + item.ObjectKey 531 } else { 532 t.Error("no object_key or link_to generated:", item.WebURI) 533 } 534 } 535 536 // It should have been exactly this 537 expectedItems := map[string]string{ 538 "/some/target/link-to-regular-file": "link-/some/target/subdir/regular-file", 539 "/some/target/some/somefile": "key-57921e8a0929eaff5003cc9dd528c3421296055a4de2ba72429dc7f41bfa8411", 540 "/some/target/some/dir/link-to-somefile": "link-/some/target/some/somefile", 541 "/some/target/subdir/rand1": "link-/rand1", 542 "/some/target/subdir/rand2": "link-/rand2", 543 "/some/target/subdir/regular-file": "key-5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", 544 "/some/target/subdir2/dir-link": "link-/some/target/subdir", 545 } 546 547 if !reflect.DeepEqual(itemMap, expectedItems) { 548 t.Error("did not publish expected items, published:", itemMap) 549 } 550 551 // It should have committed the publish (once) 552 if p.committed != 1 { 553 t.Error("expected to commit publish (once), instead p.committed ==", p.committed) 554 } 555 } 556 557 // When src tree has no trailing slash, the basename is repeated as a directory 558 // name on the destination. 559 func TestMainSyncNoSlash(t *testing.T) { 560 wd, err := os.Getwd() 561 if err != nil { 562 t.Fatal(err) 563 } 564 565 SetConfig(t, CONFIG) 566 ctrl := MockController(t) 567 568 mockGw := gw.NewMockInterface(ctrl) 569 ext.gw = mockGw 570 571 client := FakeClient{blobs: make(map[string]string)} 572 mockGw.EXPECT().NewClient(gomock.Any(), EnvMatcher{"best-env"}).Return(&client, nil) 573 574 srcPath := path.Clean(wd + "/../../test/data/srctrees/just-files") 575 576 args := []string{ 577 "rsync", 578 "-vvv", 579 srcPath, 580 "exodus:/dest", 581 } 582 583 got := Main(args) 584 585 // It should complete successfully. 586 if got != 0 { 587 t.Error("returned incorrect exit code", got) 588 } 589 590 // It should have created one publish. 591 if len(client.publishes) != 1 { 592 t.Error("expected to create 1 publish, instead created", len(client.publishes)) 593 } 594 595 p := client.publishes[0] 596 597 // Build up a URI => Key mapping of what was published 598 itemMap := make(map[string]string) 599 for _, item := range p.items { 600 if _, ok := itemMap[item.WebURI]; ok { 601 t.Error("tried to publish this URI more than once:", item.WebURI) 602 } 603 itemMap[item.WebURI] = item.ObjectKey 604 } 605 606 // It should have been exactly this 607 expectedItems := map[string]string{ 608 "/dest/just-files/hello-copy-one": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", 609 "/dest/just-files/hello-copy-two": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", 610 "/dest/just-files/subdir/some-binary": "c66f610d98b2c9fe0175a3e99ba64d7fc7de45046515ff325be56329a9347dd6", 611 } 612 613 if !reflect.DeepEqual(itemMap, expectedItems) { 614 t.Error("did not publish expected items, published:", itemMap) 615 } 616 617 // It should have committed the publish (once) 618 if p.committed != 1 { 619 t.Error("expected to commit publish (once), instead p.committed ==", p.committed) 620 } 621 } 622 623 func TestMainSyncFilesFrom(t *testing.T) { 624 wd, err := os.Getwd() 625 if err != nil { 626 t.Fatal(err) 627 } 628 629 SetConfig(t, CONFIG) 630 ctrl := MockController(t) 631 632 mockGw := gw.NewMockInterface(ctrl) 633 ext.gw = mockGw 634 635 client := FakeClient{blobs: make(map[string]string)} 636 mockGw.EXPECT().NewClient(gomock.Any(), EnvMatcher{"best-env"}).Return(&client, nil) 637 638 srcPath := path.Clean(wd + "/../../test/data") 639 filesFromPath := path.Clean(wd + "/../../test/data/source-list.txt") 640 641 args := []string{ 642 "rsync", 643 "-vvv", 644 "--files-from", filesFromPath, 645 srcPath, 646 "exodus:/dest", 647 } 648 649 got := Main(args) 650 651 // It should complete successfully. 652 if got != 0 { 653 t.Error("returned incorrect exit code", got) 654 } 655 656 // It should have created one publish. 657 if len(client.publishes) != 1 { 658 t.Error("expected to create 1 publish, instead created", len(client.publishes)) 659 } 660 661 p := client.publishes[0] 662 663 // Build up a URI => Key mapping of what was published. 664 itemMap := make(map[string]string) 665 for _, item := range p.items { 666 if _, ok := itemMap[item.WebURI]; ok { 667 t.Error("tried to publish this URI more than once:", item.WebURI) 668 } 669 itemMap[item.WebURI] = item.ObjectKey 670 } 671 672 // Paths should be comprised of the dest and the path written in the file. 673 expectPath1 := path.Join("/dest", "srctrees/just-files/subdir/some-binary") 674 expectPath2 := path.Join("/dest", "srctrees/some.conf") 675 676 // It should have been exactly this. 677 expectedItems := map[string]string{ 678 expectPath1: "c66f610d98b2c9fe0175a3e99ba64d7fc7de45046515ff325be56329a9347dd6", 679 expectPath2: "4cfe7dba345453b9e2e7a505084238095511ef673e03b6a016f871afe2dfa599", 680 } 681 682 if !reflect.DeepEqual(itemMap, expectedItems) { 683 t.Error("did not publish expected items, published:", itemMap) 684 } 685 686 // It should have committed the publish (once). 687 if p.committed != 1 { 688 t.Error("expected to commit publish (once), instead p.committed ==", p.committed) 689 } 690 } 691 692 func TestMainSyncRelative(t *testing.T) { 693 wd, err := os.Getwd() 694 if err != nil { 695 t.Fatal(err) 696 } 697 698 SetConfig(t, CONFIG) 699 ctrl := MockController(t) 700 701 mockGw := gw.NewMockInterface(ctrl) 702 ext.gw = mockGw 703 704 client := FakeClient{blobs: make(map[string]string)} 705 mockGw.EXPECT().NewClient(gomock.Any(), EnvMatcher{"best-env"}).Return(&client, nil) 706 707 srcPath := path.Clean(wd + "/../../test/data/srctrees/just-files/subdir") 708 709 args := []string{ 710 "rsync", 711 "-vvv", 712 "--relative", 713 srcPath + "/", 714 "exodus:/dest", 715 } 716 717 got := Main(args) 718 719 // It should complete successfully. 720 if got != 0 { 721 t.Error("returned incorrect exit code", got) 722 } 723 724 // It should have created one publish. 725 if len(client.publishes) != 1 { 726 t.Error("expected to create 1 publish, instead created", len(client.publishes)) 727 } 728 729 p := client.publishes[0] 730 731 // Build up a URI => Key mapping of what was published. 732 itemMap := make(map[string]string) 733 for _, item := range p.items { 734 if _, ok := itemMap[item.WebURI]; ok { 735 t.Error("tried to publish this URI more than once:", item.WebURI) 736 } 737 itemMap[item.WebURI] = item.ObjectKey 738 } 739 740 // Full source path should be preserved due to --relative. 741 expectPath := path.Join("/dest", srcPath, "some-binary") 742 743 // It should have been exactly this. 744 expectedItems := map[string]string{ 745 expectPath: "c66f610d98b2c9fe0175a3e99ba64d7fc7de45046515ff325be56329a9347dd6", 746 } 747 748 if !reflect.DeepEqual(itemMap, expectedItems) { 749 t.Error("did not publish expected items, published:", itemMap) 750 } 751 752 // It should have committed the publish (once). 753 if p.committed != 1 { 754 t.Error("expected to commit publish (once), instead p.committed ==", p.committed) 755 } 756 } 757 758 func TestMainSyncJoinPublish(t *testing.T) { 759 wd, err := os.Getwd() 760 if err != nil { 761 t.Fatal(err) 762 } 763 764 SetConfig(t, CONFIG) 765 ctrl := MockController(t) 766 767 mockGw := gw.NewMockInterface(ctrl) 768 ext.gw = mockGw 769 770 client := FakeClient{blobs: make(map[string]string)} 771 mockGw.EXPECT().NewClient(gomock.Any(), EnvMatcher{"best-env"}).Return(&client, nil) 772 773 // Set up that this publish already exists. 774 client.publishes = []FakePublish{{items: make([]gw.ItemInput, 0), id: "3e0a4539-be4a-437e-a45f-6d72f7192f17"}} 775 776 srcPath := path.Clean(wd + "/../../test/data/srctrees/just-files") 777 778 args := []string{ 779 "rsync", 780 "-vvv", 781 "--exodus-publish", "3e0a4539-be4a-437e-a45f-6d72f7192f17", 782 srcPath, 783 "exodus:/dest", 784 } 785 786 got := Main(args) 787 788 // It should complete successfully. 789 if got != 0 { 790 t.Error("returned incorrect exit code", got) 791 } 792 793 // It should have left the one publish there without creating any more 794 if len(client.publishes) != 1 { 795 t.Error("should have used 1 existing publish, instead have", len(client.publishes)) 796 } 797 798 p := client.publishes[0] 799 800 // It should NOT have committed the publish since it already existed 801 if p.committed != 0 { 802 t.Error("publish committed unexpectedly? p.committed ==", p.committed) 803 } 804 805 // Build up a URI => Key mapping of what was published 806 itemMap := make(map[string]string) 807 for _, item := range p.items { 808 if _, ok := itemMap[item.WebURI]; ok { 809 t.Error("tried to publish this URI more than once:", item.WebURI) 810 } 811 itemMap[item.WebURI] = item.ObjectKey 812 } 813 814 // It should have added these items to the publish, as normal 815 expectedItems := map[string]string{ 816 "/dest/just-files/hello-copy-one": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", 817 "/dest/just-files/hello-copy-two": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", 818 "/dest/just-files/subdir/some-binary": "c66f610d98b2c9fe0175a3e99ba64d7fc7de45046515ff325be56329a9347dd6", 819 } 820 821 if !reflect.DeepEqual(itemMap, expectedItems) { 822 t.Error("did not publish expected items, published:", itemMap) 823 } 824 } 825 826 func TestMainStripFromPrefix(t *testing.T) { 827 wd, err := os.Getwd() 828 if err != nil { 829 t.Fatal(err) 830 } 831 832 SetConfig(t, CONFIG) 833 ctrl := MockController(t) 834 835 mockGw := gw.NewMockInterface(ctrl) 836 ext.gw = mockGw 837 838 client := FakeClient{blobs: make(map[string]string)} 839 mockGw.EXPECT().NewClient(gomock.Any(), EnvMatcher{"best-env"}).Return(&client, nil) 840 841 srcPath := path.Clean(wd + "/../../test/data/srctrees/just-files") 842 843 args := []string{ 844 "rsync", 845 srcPath + "/", 846 "otherhost:/foo/bar/baz/my/dest", 847 } 848 849 got := Main(args) 850 851 // It should complete successfully. 852 if got != 0 { 853 t.Error("returned incorrect exit code", got) 854 } 855 856 // Check paths of some blobs we expected to deal with. 857 binPath := client.blobs["c66f610d98b2c9fe0175a3e99ba64d7fc7de45046515ff325be56329a9347dd6"] 858 helloPath := client.blobs["5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03"] 859 860 // It should have uploaded the binary from here 861 if binPath != srcPath+"/subdir/some-binary" { 862 t.Error("binary uploaded from unexpected path", binPath) 863 } 864 865 // For the hello file, since there were two copies, it's undefined which one of them 866 // was used for the upload - but should be one of them. 867 if helloPath != srcPath+"/hello-copy-one" && helloPath != srcPath+"/hello-copy-two" { 868 t.Error("hello uploaded from unexpected path", helloPath) 869 } 870 871 // It should have created one publish. 872 if len(client.publishes) != 1 { 873 t.Error("expected to create 1 publish, instead created", len(client.publishes)) 874 } 875 876 p := client.publishes[0] 877 878 // Build up a URI => Key mapping of what was published 879 itemMap := make(map[string]string) 880 for _, item := range p.items { 881 if _, ok := itemMap[item.WebURI]; ok { 882 t.Error("tried to publish this URI more than once:", item.WebURI) 883 } 884 itemMap[item.WebURI] = item.ObjectKey 885 } 886 887 // It should have been exactly this 888 expectedItems := map[string]string{ 889 "/bar/baz/my/dest/subdir/some-binary": "c66f610d98b2c9fe0175a3e99ba64d7fc7de45046515ff325be56329a9347dd6", 890 "/bar/baz/my/dest/hello-copy-one": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", 891 "/bar/baz/my/dest/hello-copy-two": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", 892 } 893 894 if !reflect.DeepEqual(itemMap, expectedItems) { 895 t.Error("did not publish expected items, published:", itemMap) 896 } 897 898 // It should have committed the publish (once) 899 if p.committed != 1 { 900 t.Error("expected to commit publish (once), instead p.committed ==", p.committed) 901 } 902 } 903 904 func TestMainStripDefaultPrefix(t *testing.T) { 905 wd, err := os.Getwd() 906 if err != nil { 907 t.Fatal(err) 908 } 909 910 SetConfig(t, CONFIG) 911 ctrl := MockController(t) 912 913 mockGw := gw.NewMockInterface(ctrl) 914 ext.gw = mockGw 915 916 client := FakeClient{blobs: make(map[string]string)} 917 mockGw.EXPECT().NewClient(gomock.Any(), EnvMatcher{"best-env"}).Return(&client, nil) 918 919 srcPath := path.Clean(wd + "/../../test/data/srctrees/just-files") 920 921 args := []string{ 922 "rsync", 923 "-vvv", 924 srcPath + "/", 925 "somehost:/cdn/root/my/dest", 926 } 927 928 got := Main(args) 929 930 // It should complete successfully. 931 if got != 0 { 932 t.Error("returned incorrect exit code", got) 933 } 934 935 // Check paths of some blobs we expected to deal with. 936 binPath := client.blobs["c66f610d98b2c9fe0175a3e99ba64d7fc7de45046515ff325be56329a9347dd6"] 937 helloPath := client.blobs["5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03"] 938 939 // It should have uploaded the binary from here 940 if binPath != srcPath+"/subdir/some-binary" { 941 t.Error("binary uploaded from unexpected path", binPath) 942 } 943 944 // For the hello file, since there were two copies, it's undefined which one of them 945 // was used for the upload - but should be one of them. 946 if helloPath != srcPath+"/hello-copy-one" && helloPath != srcPath+"/hello-copy-two" { 947 t.Error("hello uploaded from unexpected path", helloPath) 948 } 949 950 // It should have created one publish. 951 if len(client.publishes) != 1 { 952 t.Error("expected to create 1 publish, instead created", len(client.publishes)) 953 } 954 955 p := client.publishes[0] 956 957 // Build up a URI => Key mapping of what was published 958 itemMap := make(map[string]string) 959 for _, item := range p.items { 960 if _, ok := itemMap[item.WebURI]; ok { 961 t.Error("tried to publish this URI more than once:", item.WebURI) 962 } 963 itemMap[item.WebURI] = item.ObjectKey 964 } 965 966 // It should have been exactly this 967 expectedItems := map[string]string{ 968 "/my/dest/subdir/some-binary": "c66f610d98b2c9fe0175a3e99ba64d7fc7de45046515ff325be56329a9347dd6", 969 "/my/dest/hello-copy-one": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", 970 "/my/dest/hello-copy-two": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", 971 } 972 973 if !reflect.DeepEqual(itemMap, expectedItems) { 974 t.Error("did not publish expected items, published:", itemMap) 975 } 976 977 // It should have committed the publish (once) 978 if p.committed != 1 { 979 t.Error("expected to commit publish (once), instead p.committed ==", p.committed) 980 } 981 } 982 983 func TestMainSyncSingleFile(t *testing.T) { 984 wd, err := os.Getwd() 985 if err != nil { 986 t.Fatal(err) 987 } 988 989 SetConfig(t, CONFIG) 990 ctrl := MockController(t) 991 992 mockGw := gw.NewMockInterface(ctrl) 993 ext.gw = mockGw 994 995 client := FakeClient{blobs: make(map[string]string)} 996 mockGw.EXPECT().NewClient(gomock.Any(), EnvMatcher{"best-env"}).Return(&client, nil) 997 998 srcPath := path.Clean(wd + "/../../test/data/srctrees/single-file/test") 999 1000 args := []string{ 1001 "rsync", 1002 "-vvv", 1003 srcPath, 1004 "exodus:/dest/test", 1005 } 1006 1007 got := Main(args) 1008 1009 // It should complete successfully. 1010 if got != 0 { 1011 t.Error("returned incorrect exit code", got) 1012 } 1013 1014 // It should have created one publish. 1015 if len(client.publishes) != 1 { 1016 t.Error("expected to create 1 publish, instead created", len(client.publishes)) 1017 } 1018 1019 p := client.publishes[0] 1020 1021 // Build up a URI => Key mapping of what was published 1022 itemMap := make(map[string]string) 1023 for _, item := range p.items { 1024 if _, ok := itemMap[item.WebURI]; ok { 1025 t.Error("tried to publish this URI more than once:", item.WebURI) 1026 } 1027 itemMap[item.WebURI] = item.ObjectKey 1028 } 1029 1030 // It should have been exactly this 1031 expectedItems := map[string]string{ 1032 "/dest/test": "98ea6e4f216f2fb4b69fff9b3a44842c38686ca685f3f55dc48c5d3fb1107be4", 1033 } 1034 1035 if !reflect.DeepEqual(itemMap, expectedItems) { 1036 t.Error("did not publish expected items, published:", itemMap) 1037 } 1038 1039 // It should have committed the publish (once) 1040 if p.committed != 1 { 1041 t.Error("expected to commit publish (once), instead p.committed ==", p.committed) 1042 } 1043 } 1044 1045 func TestMainTypicalSyncWithExistingItems(t *testing.T) { 1046 wd, err := os.Getwd() 1047 if err != nil { 1048 t.Fatal(err) 1049 } 1050 1051 SetConfig(t, CONFIG) 1052 ctrl := MockController(t) 1053 1054 mockGw := gw.NewMockInterface(ctrl) 1055 ext.gw = mockGw 1056 1057 srcPath := path.Clean(wd + "/../../test/data/srctrees/just-files") 1058 1059 client := FakeClient{blobs: make(map[string]string)} 1060 mockGw.EXPECT().NewClient(gomock.Any(), EnvMatcher{"best-env"}).Return(&client, nil) 1061 1062 // The hello file already exists in our bucket 1063 client.blobs["5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03"] = "/some/other/source/some-file" 1064 1065 args := []string{ 1066 "rsync", 1067 srcPath + "/", 1068 "exodus:/some/target", 1069 } 1070 1071 got := Main(args) 1072 1073 // It should complete successfully. 1074 if got != 0 { 1075 t.Error("returned incorrect exit code", got) 1076 } 1077 1078 p := client.publishes[0] 1079 blobs := client.blobs 1080 1081 // Build up a URI => Key mapping of what was uploaded 1082 itemMap := make(map[string]string) 1083 for _, item := range p.items { 1084 if _, ok := itemMap[item.WebURI]; ok { 1085 t.Error("tried to publish this URI more than once:", item.WebURI) 1086 } 1087 itemMap[item.WebURI] = item.ObjectKey 1088 } 1089 1090 // Only the binary file should have been uploaded. 1091 // The hello file already existed in the bucket and thus should not have been re-uploaded. 1092 expectedUploadedItems := map[string]string{ 1093 "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03": "/some/other/source/some-file", 1094 "c66f610d98b2c9fe0175a3e99ba64d7fc7de45046515ff325be56329a9347dd6": srcPath + "/subdir/some-binary", 1095 } 1096 if !reflect.DeepEqual(blobs, expectedUploadedItems) { 1097 t.Error("did not upload expected items, uploaded:", blobs) 1098 } 1099 1100 // The hello files should be published despite already existing in the bucket 1101 expectedPublishedItems := map[string]string{ 1102 "/some/target/subdir/some-binary": "c66f610d98b2c9fe0175a3e99ba64d7fc7de45046515ff325be56329a9347dd6", 1103 "/some/target/hello-copy-one": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", 1104 "/some/target/hello-copy-two": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", 1105 } 1106 if !reflect.DeepEqual(itemMap, expectedPublishedItems) { 1107 t.Error("did not publish expected items, published:", itemMap) 1108 } 1109 }