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  }