github.com/thiagoyeds/go-cloud@v0.26.0/blob/blob_test.go (about)

     1  // Copyright 2018 The Go Cloud Development Kit Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package blob
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  	"io"
    23  	"net/url"
    24  	"strings"
    25  	"sync"
    26  	"testing"
    27  
    28  	"github.com/google/go-cmp/cmp"
    29  	"gocloud.dev/blob/driver"
    30  	"gocloud.dev/gcerrors"
    31  	"gocloud.dev/internal/gcerr"
    32  )
    33  
    34  var (
    35  	errFake     = errors.New("fake")
    36  	errNotFound = errors.New("fake not found")
    37  )
    38  
    39  func TestExists(t *testing.T) {
    40  	tests := []struct {
    41  		Description string
    42  		Err         error
    43  		Want        bool
    44  		WantErr     bool
    45  	}{
    46  		{
    47  			Description: "no error -> exists",
    48  			Err:         nil,
    49  			Want:        true,
    50  			WantErr:     false,
    51  		},
    52  		{
    53  			Description: "notfound error -> !exists",
    54  			Err:         errNotFound,
    55  			Want:        false,
    56  			WantErr:     false,
    57  		},
    58  		{
    59  			Description: "other error -> error",
    60  			Err:         errFake,
    61  			Want:        false,
    62  			WantErr:     true,
    63  		},
    64  	}
    65  
    66  	for _, test := range tests {
    67  		t.Run(test.Description, func(t *testing.T) {
    68  			drv := &fakeAttributes{attributesErr: test.Err}
    69  			b := NewBucket(drv)
    70  			defer b.Close()
    71  			got, gotErr := b.Exists(context.Background(), "key")
    72  			if got != test.Want {
    73  				t.Errorf("got %v want %v", got, test.Want)
    74  			}
    75  			if (gotErr != nil) != test.WantErr {
    76  				t.Errorf("got err %v want %v", gotErr, test.WantErr)
    77  			}
    78  		})
    79  	}
    80  }
    81  
    82  // fakeAttributes implements driver.Bucket. Only Attributes is implemented,
    83  // returning a zero Attributes struct and attributesErr.
    84  type fakeAttributes struct {
    85  	driver.Bucket
    86  	attributesErr error
    87  }
    88  
    89  func (b *fakeAttributes) Attributes(ctx context.Context, key string) (*driver.Attributes, error) {
    90  	if b.attributesErr != nil {
    91  		return nil, b.attributesErr
    92  	}
    93  	return &driver.Attributes{}, nil
    94  }
    95  
    96  func (b *fakeAttributes) ErrorCode(err error) gcerrors.ErrorCode {
    97  	if err == errNotFound {
    98  		return gcerrors.NotFound
    99  	}
   100  	return gcerrors.Unknown
   101  }
   102  
   103  func (b *fakeAttributes) Close() error { return nil }
   104  
   105  // Verify that ListIterator works even if driver.ListPaged returns empty pages.
   106  func TestListIterator(t *testing.T) {
   107  	ctx := context.Background()
   108  	want := []string{"a", "b", "c"}
   109  	db := &fakeLister{
   110  		pages:         [][]string{{"a"}, {}, {}, {"b", "c"}, {}, {}},
   111  		wantPageSizes: []int{0, 0, 0, 0, 0, 0},
   112  	}
   113  	b := NewBucket(db)
   114  	defer b.Close()
   115  	iter := b.List(nil)
   116  	var got []string
   117  	for {
   118  		obj, err := iter.Next(ctx)
   119  		if err == io.EOF {
   120  			break
   121  		}
   122  		if err != nil {
   123  			t.Fatal(err)
   124  		}
   125  		got = append(got, obj.Key)
   126  	}
   127  	if !cmp.Equal(got, want) {
   128  		t.Errorf("got %v, want %v", got, want)
   129  	}
   130  }
   131  
   132  // Verify that ListPage works even if driver.ListPaged returns empty pages.
   133  func TestListPage(t *testing.T) {
   134  	ctx := context.Background()
   135  	want := [][]string{{"a", "b"}, {"c", "d"}, {"e"}}
   136  	db := &fakeLister{
   137  		pages:         [][]string{{}, {"a", "b"}, {}, {}, {"c"}, {}, {"d"}, {}, {}, {"e"}},
   138  		wantPageSizes: []int{2, 2, 2, 2, 2, 1, 1, 2, 2, 2},
   139  	}
   140  	b := NewBucket(db)
   141  	defer b.Close()
   142  
   143  	nextToken := FirstPageToken
   144  	got := [][]string{}
   145  	for {
   146  		page, token, err := b.ListPage(ctx, nextToken, 2, nil)
   147  		if err == io.EOF {
   148  			break
   149  		}
   150  		if err != nil {
   151  			t.Fatal(err)
   152  		}
   153  		gotPage := make([]string, len(page))
   154  		for i, o := range page {
   155  			gotPage[i] = o.Key
   156  		}
   157  		got = append(got, gotPage)
   158  		nextToken = token
   159  	}
   160  	if !cmp.Equal(got, want) {
   161  		t.Errorf("got %v, want %v", got, want)
   162  	}
   163  }
   164  
   165  // fakeLister implements driver.Bucket. Only ListPaged is implemented,
   166  // returning static data from pages.
   167  type fakeLister struct {
   168  	driver.Bucket
   169  	pages         [][]string
   170  	wantPageSizes []int
   171  }
   172  
   173  func (b *fakeLister) ListPaged(ctx context.Context, opts *driver.ListOptions) (*driver.ListPage, error) {
   174  	if len(b.pages) != len(b.wantPageSizes) {
   175  		return nil, fmt.Errorf("invalid fakeLister setup")
   176  	}
   177  	if len(b.pages) == 0 {
   178  		return &driver.ListPage{}, nil
   179  	}
   180  	page := b.pages[0]
   181  	wantPageSize := b.wantPageSizes[0]
   182  	b.pages = b.pages[1:]
   183  	b.wantPageSizes = b.wantPageSizes[1:]
   184  	if opts.PageSize != wantPageSize {
   185  		return nil, fmt.Errorf("got page size %d, want %d", opts.PageSize, wantPageSize)
   186  	}
   187  	var objs []*driver.ListObject
   188  	for _, key := range page {
   189  		objs = append(objs, &driver.ListObject{Key: key})
   190  	}
   191  	return &driver.ListPage{Objects: objs, NextPageToken: []byte{1}}, nil
   192  }
   193  
   194  func (*fakeLister) Close() error                           { return nil }
   195  func (*fakeLister) ErrorCode(err error) gcerrors.ErrorCode { return gcerrors.Unknown }
   196  
   197  // erroringBucket implements driver.Bucket. All interface methods that return
   198  // errors are implemented, and return errFake.
   199  // In addition, when passed the key "work", NewRangeReader and NewTypedWriter
   200  // will return a Reader/Writer respectively, that always return errFake
   201  // from Read/Write and Close.
   202  type erroringBucket struct {
   203  	driver.Bucket
   204  }
   205  
   206  type erroringReader struct {
   207  	driver.Reader
   208  }
   209  
   210  func (r *erroringReader) Read(p []byte) (int, error) {
   211  	return 0, errFake
   212  }
   213  
   214  func (r *erroringReader) Close() error {
   215  	return errFake
   216  }
   217  
   218  type erroringWriter struct {
   219  	driver.Writer
   220  }
   221  
   222  func (r *erroringWriter) Write(p []byte) (int, error) {
   223  	return 0, errFake
   224  }
   225  
   226  func (r *erroringWriter) Close() error {
   227  	return errFake
   228  }
   229  
   230  func (b *erroringBucket) Attributes(ctx context.Context, key string) (*driver.Attributes, error) {
   231  	return nil, errFake
   232  }
   233  
   234  func (b *erroringBucket) ListPaged(ctx context.Context, opts *driver.ListOptions) (*driver.ListPage, error) {
   235  	return nil, errFake
   236  }
   237  
   238  func (b *erroringBucket) NewRangeReader(ctx context.Context, key string, offset, length int64, opts *driver.ReaderOptions) (driver.Reader, error) {
   239  	if key == "work" {
   240  		return &erroringReader{}, nil
   241  	}
   242  	return nil, errFake
   243  }
   244  
   245  func (b *erroringBucket) NewTypedWriter(ctx context.Context, key string, contentType string, opts *driver.WriterOptions) (driver.Writer, error) {
   246  	if key == "work" {
   247  		return &erroringWriter{}, nil
   248  	}
   249  	return nil, errFake
   250  }
   251  
   252  func (b *erroringBucket) Copy(ctx context.Context, dstKey, srcKey string, opts *driver.CopyOptions) error {
   253  	return errFake
   254  }
   255  
   256  func (b *erroringBucket) Delete(ctx context.Context, key string) error {
   257  	return errFake
   258  }
   259  
   260  func (b *erroringBucket) SignedURL(ctx context.Context, key string, opts *driver.SignedURLOptions) (string, error) {
   261  	return "", errFake
   262  }
   263  
   264  func (b *erroringBucket) Close() error {
   265  	return errFake
   266  }
   267  
   268  func (b *erroringBucket) ErrorCode(err error) gcerrors.ErrorCode {
   269  	return gcerrors.Unknown
   270  }
   271  
   272  // TestErrorsAreWrapped tests that all errors returned from the driver are
   273  // wrapped exactly once by the portable type.
   274  func TestErrorsAreWrapped(t *testing.T) {
   275  	ctx := context.Background()
   276  	buf := bytes.Repeat([]byte{'A'}, sniffLen)
   277  	b := NewBucket(&erroringBucket{})
   278  
   279  	// verifyWrap ensures that err is wrapped exactly once.
   280  	verifyWrap := func(description string, err error) {
   281  		if err == nil {
   282  			t.Errorf("%s: got nil error, wanted non-nil", description)
   283  			return
   284  		}
   285  		if _, ok := err.(*gcerr.Error); !ok {
   286  			t.Errorf("%s: not wrapped: %v", description, err)
   287  		}
   288  		if s := err.Error(); !strings.HasPrefix(s, "blob ") {
   289  			t.Logf("short form of error: %v", err)
   290  			t.Logf("with details: %+v", err)
   291  			t.Errorf("%s: Error() for wrapped error doesn't start with blob: prefix: %s", description, s)
   292  		}
   293  	}
   294  
   295  	_, err := b.Attributes(ctx, "")
   296  	verifyWrap("Attributes", err)
   297  
   298  	iter := b.List(nil)
   299  	_, err = iter.Next(ctx)
   300  	verifyWrap("ListIterator.Next", err)
   301  
   302  	_, err = b.NewRangeReader(ctx, "", 0, 1, nil)
   303  	verifyWrap("NewRangeReader", err)
   304  	_, err = b.ReadAll(ctx, "")
   305  	verifyWrap("ReadAll", err)
   306  
   307  	// Providing ContentType means driver.NewTypedWriter is called right away.
   308  	_, err = b.NewWriter(ctx, "", &WriterOptions{ContentType: "foo"})
   309  	verifyWrap("NewWriter", err)
   310  	err = b.WriteAll(ctx, "", buf, &WriterOptions{ContentType: "foo"})
   311  	verifyWrap("WriteAll", err)
   312  
   313  	// Not providing ContentType means driver.NewTypedWriter is only called
   314  	// after writing sniffLen bytes.
   315  	w, _ := b.NewWriter(ctx, "", nil)
   316  	_, err = w.Write(buf)
   317  	verifyWrap("NewWriter (no ContentType)", err)
   318  	w.Close()
   319  	err = b.WriteAll(ctx, "", buf, nil)
   320  	verifyWrap("WriteAll (no ContentType)", err)
   321  
   322  	r, _ := b.NewRangeReader(ctx, "work", 0, 1, nil)
   323  	_, err = r.Read(buf)
   324  	verifyWrap("Reader.Read", err)
   325  
   326  	err = r.Close()
   327  	verifyWrap("Reader.Close", err)
   328  
   329  	w, _ = b.NewWriter(ctx, "work", &WriterOptions{ContentType: "foo"})
   330  	_, err = w.Write(buf)
   331  	verifyWrap("Writer.Write", err)
   332  
   333  	err = w.Close()
   334  	verifyWrap("Writer.Close", err)
   335  
   336  	err = b.Copy(ctx, "", "", nil)
   337  	verifyWrap("Copy", err)
   338  
   339  	err = b.Delete(ctx, "")
   340  	verifyWrap("Delete", err)
   341  
   342  	_, err = b.SignedURL(ctx, "", nil)
   343  	verifyWrap("SignedURL", err)
   344  
   345  	err = b.Close()
   346  	verifyWrap("Close", err)
   347  }
   348  
   349  var (
   350  	testOpenOnce sync.Once
   351  	testOpenGot  *url.URL
   352  )
   353  
   354  // TestBucketIsClosed verifies that all Bucket functions return an error
   355  // if the Bucket is closed.
   356  func TestBucketIsClosed(t *testing.T) {
   357  	ctx := context.Background()
   358  	buf := bytes.Repeat([]byte{'A'}, sniffLen)
   359  
   360  	bucket := NewBucket(&erroringBucket{})
   361  	bucket.Close()
   362  
   363  	if _, err := bucket.Attributes(ctx, ""); err != errClosed {
   364  		t.Error(err)
   365  	}
   366  	iter := bucket.List(nil)
   367  	if _, err := iter.Next(ctx); err != errClosed {
   368  		t.Error(err)
   369  	}
   370  
   371  	if _, err := bucket.NewRangeReader(ctx, "", 0, 1, nil); err != errClosed {
   372  		t.Error(err)
   373  	}
   374  	if _, err := bucket.ReadAll(ctx, ""); err != errClosed {
   375  		t.Error(err)
   376  	}
   377  	if _, err := bucket.NewWriter(ctx, "", nil); err != errClosed {
   378  		t.Error(err)
   379  	}
   380  	if err := bucket.WriteAll(ctx, "", buf, nil); err != errClosed {
   381  		t.Error(err)
   382  	}
   383  	if _, err := bucket.NewRangeReader(ctx, "work", 0, 1, nil); err != errClosed {
   384  		t.Error(err)
   385  	}
   386  	if err := bucket.Copy(ctx, "", "", nil); err != errClosed {
   387  		t.Error(err)
   388  	}
   389  	if err := bucket.Delete(ctx, ""); err != errClosed {
   390  		t.Error(err)
   391  	}
   392  	if _, err := bucket.SignedURL(ctx, "", nil); err != errClosed {
   393  		t.Error(err)
   394  	}
   395  	if err := bucket.Close(); err != errClosed {
   396  		t.Error(err)
   397  	}
   398  }
   399  
   400  func TestURLMux(t *testing.T) {
   401  	ctx := context.Background()
   402  
   403  	mux := new(URLMux)
   404  	fake := &fakeOpener{}
   405  	mux.RegisterBucket("foo", fake)
   406  	mux.RegisterBucket("err", fake)
   407  
   408  	if diff := cmp.Diff(mux.BucketSchemes(), []string{"err", "foo"}); diff != "" {
   409  		t.Errorf("Schemes: %s", diff)
   410  	}
   411  	if !mux.ValidBucketScheme("foo") || !mux.ValidBucketScheme("err") {
   412  		t.Errorf("ValidBucketScheme didn't return true for valid scheme")
   413  	}
   414  	if mux.ValidBucketScheme("foo2") || mux.ValidBucketScheme("http") {
   415  		t.Errorf("ValidBucketScheme didn't return false for invalid scheme")
   416  	}
   417  
   418  	for _, tc := range []struct {
   419  		name    string
   420  		url     string
   421  		wantErr bool
   422  	}{
   423  		{
   424  			name:    "empty URL",
   425  			wantErr: true,
   426  		},
   427  		{
   428  			name:    "invalid URL",
   429  			url:     ":foo",
   430  			wantErr: true,
   431  		},
   432  		{
   433  			name:    "invalid URL no scheme",
   434  			url:     "foo",
   435  			wantErr: true,
   436  		},
   437  		{
   438  			name:    "unregistered scheme",
   439  			url:     "bar://mybucket",
   440  			wantErr: true,
   441  		},
   442  		{
   443  			name:    "func returns error",
   444  			url:     "err://mybucket",
   445  			wantErr: true,
   446  		},
   447  		{
   448  			name: "no query options",
   449  			url:  "foo://mybucket",
   450  		},
   451  		{
   452  			name: "empty query options",
   453  			url:  "foo://mybucket?",
   454  		},
   455  		{
   456  			name: "query options",
   457  			url:  "foo://mybucket?aAa=bBb&cCc=dDd",
   458  		},
   459  		{
   460  			name: "multiple query options",
   461  			url:  "foo://mybucket?x=a&x=b&x=c",
   462  		},
   463  		{
   464  			name: "fancy bucket name",
   465  			url:  "foo:///foo/bar/baz",
   466  		},
   467  		{
   468  			name: "using api scheme prefix",
   469  			url:  "blob+foo:///foo/bar/baz",
   470  		},
   471  		{
   472  			name: "using api+type scheme prefix",
   473  			url:  "blob+bucket+foo:///foo/bar/baz",
   474  		},
   475  	} {
   476  		t.Run(tc.name, func(t *testing.T) {
   477  			_, gotErr := mux.OpenBucket(ctx, tc.url)
   478  			if (gotErr != nil) != tc.wantErr {
   479  				t.Fatalf("got err %v, want error %v", gotErr, tc.wantErr)
   480  			}
   481  			if gotErr != nil {
   482  				return
   483  			}
   484  			if got := fake.u.String(); got != tc.url {
   485  				t.Errorf("got %q want %q", got, tc.url)
   486  			}
   487  			// Repeat with OpenBucketURL.
   488  			parsed, err := url.Parse(tc.url)
   489  			if err != nil {
   490  				t.Fatal(err)
   491  			}
   492  			_, gotErr = mux.OpenBucketURL(ctx, parsed)
   493  			if gotErr != nil {
   494  				t.Fatalf("got err %v want nil", gotErr)
   495  			}
   496  			if got := fake.u.String(); got != tc.url {
   497  				t.Errorf("got %q want %q", got, tc.url)
   498  			}
   499  		})
   500  	}
   501  }
   502  
   503  type fakeOpener struct {
   504  	u *url.URL // last url passed to OpenBucketURL
   505  }
   506  
   507  func (o *fakeOpener) OpenBucketURL(ctx context.Context, u *url.URL) (*Bucket, error) {
   508  	if u.Scheme == "err" {
   509  		return nil, errors.New("fail")
   510  	}
   511  	o.u = u
   512  	return nil, nil
   513  }