github.com/aavshr/aws-sdk-go@v1.41.3/service/s3/statusok_error_test.go (about)

     1  //go:build go1.7
     2  // +build go1.7
     3  
     4  package s3_test
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"strconv"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/aavshr/aws-sdk-go/aws"
    18  	"github.com/aavshr/aws-sdk-go/aws/awserr"
    19  	"github.com/aavshr/aws-sdk-go/aws/corehandlers"
    20  	"github.com/aavshr/aws-sdk-go/aws/request"
    21  	"github.com/aavshr/aws-sdk-go/awstesting/unit"
    22  	"github.com/aavshr/aws-sdk-go/service/s3"
    23  )
    24  
    25  const errMsg = `<Error><Code>ErrorCode</Code><Message>message body</Message><RequestId>requestID</RequestId><HostId>hostID=</HostId></Error>`
    26  const xmlPreambleMsg = `<?xml version="1.0" encoding="UTF-8"?>`
    27  
    28  var lastModifiedTime = time.Date(2009, 11, 23, 0, 0, 0, 0, time.UTC)
    29  
    30  func TestCopyObjectNoError(t *testing.T) {
    31  	const successMsg = `
    32  <?xml version="1.0" encoding="UTF-8"?>
    33  <CopyObjectResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><LastModified>2009-11-23T0:00:00Z</LastModified><ETag>&quot;1da64c7f13d1e8dbeaea40b905fd586c&quot;</ETag></CopyObjectResult>`
    34  
    35  	var responseBodyClosed bool
    36  	res, err := newCopyTestSvc(successMsg, &responseBodyClosed).CopyObject(&s3.CopyObjectInput{
    37  		Bucket:     aws.String("bucketname"),
    38  		CopySource: aws.String("bucketname/exists.txt"),
    39  		Key:        aws.String("destination.txt"),
    40  	})
    41  
    42  	if err != nil {
    43  		t.Fatalf("expected no error, but received %v", err)
    44  	}
    45  	if e, a := fmt.Sprintf(`%q`, "1da64c7f13d1e8dbeaea40b905fd586c"), *res.CopyObjectResult.ETag; e != a {
    46  		t.Errorf("expected %s, but received %s", e, a)
    47  	}
    48  	if e, a := lastModifiedTime, *res.CopyObjectResult.LastModified; !e.Equal(a) {
    49  		t.Errorf("expected %v, but received %v", e, a)
    50  	}
    51  	if !responseBodyClosed {
    52  		t.Error("http response body is not closed")
    53  	}
    54  }
    55  
    56  func TestCopyObjectError(t *testing.T) {
    57  	var responseBodyClosed bool
    58  	_, err := newCopyTestSvc(errMsg, &responseBodyClosed).CopyObject(&s3.CopyObjectInput{
    59  		Bucket:     aws.String("bucketname"),
    60  		CopySource: aws.String("bucketname/doesnotexist.txt"),
    61  		Key:        aws.String("destination.txt"),
    62  	})
    63  
    64  	if err == nil {
    65  		t.Error("expected error, but received none")
    66  	}
    67  	e := err.(awserr.Error)
    68  
    69  	if e, a := "ErrorCode", e.Code(); e != a {
    70  		t.Errorf("expected %s, but received %s", e, a)
    71  	}
    72  	if e, a := "message body", e.Message(); e != a {
    73  		t.Errorf("expected %s, but received %s", e, a)
    74  	}
    75  	if !responseBodyClosed {
    76  		t.Error("http response body is not closed")
    77  	}
    78  }
    79  
    80  func TestUploadPartCopySuccess(t *testing.T) {
    81  	const successMsg = `
    82  <?xml version="1.0" encoding="UTF-8"?>
    83  <UploadPartCopyResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><LastModified>2009-11-23T0:00:00Z</LastModified><ETag>&quot;1da64c7f13d1e8dbeaea40b905fd586c&quot;</ETag></UploadPartCopyResult>`
    84  
    85  	var responseBodyClosed bool
    86  	res, err := newCopyTestSvc(successMsg, &responseBodyClosed).UploadPartCopy(&s3.UploadPartCopyInput{
    87  		Bucket:     aws.String("bucketname"),
    88  		CopySource: aws.String("bucketname/doesnotexist.txt"),
    89  		Key:        aws.String("destination.txt"),
    90  		PartNumber: aws.Int64(0),
    91  		UploadId:   aws.String("uploadID"),
    92  	})
    93  
    94  	if err != nil {
    95  		t.Errorf("expected no error, but received %v", err)
    96  	}
    97  
    98  	if e, a := fmt.Sprintf(`%q`, "1da64c7f13d1e8dbeaea40b905fd586c"), *res.CopyPartResult.ETag; e != a {
    99  		t.Errorf("expected %s, but received %s", e, a)
   100  	}
   101  	if e, a := lastModifiedTime, *res.CopyPartResult.LastModified; !e.Equal(a) {
   102  		t.Errorf("expected %v, but received %v", e, a)
   103  	}
   104  	if !responseBodyClosed {
   105  		t.Error("http response body is not closed")
   106  	}
   107  }
   108  
   109  func TestUploadPartCopyError(t *testing.T) {
   110  	var responseBodyClosed bool
   111  	_, err := newCopyTestSvc(errMsg, &responseBodyClosed).UploadPartCopy(&s3.UploadPartCopyInput{
   112  		Bucket:     aws.String("bucketname"),
   113  		CopySource: aws.String("bucketname/doesnotexist.txt"),
   114  		Key:        aws.String("destination.txt"),
   115  		PartNumber: aws.Int64(0),
   116  		UploadId:   aws.String("uploadID"),
   117  	})
   118  
   119  	if err == nil {
   120  		t.Error("expected an error")
   121  	}
   122  	e := err.(awserr.Error)
   123  
   124  	if e, a := "ErrorCode", e.Code(); e != a {
   125  		t.Errorf("expected %s, but received %s", e, a)
   126  	}
   127  	if e, a := "message body", e.Message(); e != a {
   128  		t.Errorf("expected %s, but received %s", e, a)
   129  	}
   130  	if !responseBodyClosed {
   131  		t.Error("http response body is not closed")
   132  	}
   133  }
   134  
   135  func TestCompleteMultipartUploadSuccess(t *testing.T) {
   136  	const successMsg = `
   137  <?xml version="1.0" encoding="UTF-8"?>
   138  <CompleteMultipartUploadResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Location>locationName</Location><Bucket>bucketName</Bucket><Key>keyName</Key><ETag>"etagVal"</ETag></CompleteMultipartUploadResult>`
   139  
   140  	var responseBodyClosed bool
   141  	res, err := newCopyTestSvc(successMsg, &responseBodyClosed).CompleteMultipartUpload(&s3.CompleteMultipartUploadInput{
   142  		Bucket:   aws.String("bucketname"),
   143  		Key:      aws.String("key"),
   144  		UploadId: aws.String("uploadID"),
   145  	})
   146  
   147  	if err != nil {
   148  		t.Errorf("expected no error, but received %v", err)
   149  	}
   150  
   151  	if e, a := `"etagVal"`, *res.ETag; e != a {
   152  		t.Errorf("expected %s, but received %s", e, a)
   153  	}
   154  	if e, a := "bucketName", *res.Bucket; e != a {
   155  		t.Errorf("expected %s, but received %s", e, a)
   156  	}
   157  	if e, a := "keyName", *res.Key; e != a {
   158  		t.Errorf("expected %s, but received %s", e, a)
   159  	}
   160  	if e, a := "locationName", *res.Location; e != a {
   161  		t.Errorf("expected %s, but received %s", e, a)
   162  	}
   163  	if !responseBodyClosed {
   164  		t.Error("http response body is not closed")
   165  	}
   166  }
   167  
   168  func TestCompleteMultipartUploadError(t *testing.T) {
   169  	var responseBodyClosed bool
   170  	_, err := newCopyTestSvc(errMsg, &responseBodyClosed).CompleteMultipartUpload(&s3.CompleteMultipartUploadInput{
   171  		Bucket:   aws.String("bucketname"),
   172  		Key:      aws.String("key"),
   173  		UploadId: aws.String("uploadID"),
   174  	})
   175  
   176  	if err == nil {
   177  		t.Error("expected an error")
   178  	}
   179  	e := err.(awserr.Error)
   180  
   181  	if e, a := "ErrorCode", e.Code(); e != a {
   182  		t.Errorf("expected %s, but received %s", e, a)
   183  	}
   184  	if e, a := "message body", e.Message(); e != a {
   185  		t.Errorf("expected %s, but received %s", e, a)
   186  	}
   187  	if !responseBodyClosed {
   188  		t.Error("http response body is not closed")
   189  	}
   190  }
   191  
   192  func newCopyTestSvc(errMsg string, responseBodyClosed *bool) *s3.S3 {
   193  	const statusCode = http.StatusOK
   194  
   195  	svc := s3.New(unit.Session, &aws.Config{
   196  		MaxRetries: aws.Int(0),
   197  		SleepDelay: func(time.Duration) {},
   198  	})
   199  
   200  	closeChecker := func() error {
   201  		if responseBodyClosed != nil {
   202  			*responseBodyClosed = true
   203  		}
   204  		return nil
   205  	}
   206  
   207  	svc.Handlers.Send.Swap(corehandlers.SendHandler.Name,
   208  		request.NamedHandler{
   209  			Name: "newCopyTestSvc",
   210  			Fn: func(r *request.Request) {
   211  				io.Copy(ioutil.Discard, r.HTTPRequest.Body)
   212  				r.HTTPRequest.Body.Close()
   213  				r.HTTPResponse = &http.Response{
   214  					Status:     http.StatusText(statusCode),
   215  					StatusCode: statusCode,
   216  					Header:     http.Header{},
   217  					Body:       newFuncCloser(strings.NewReader(errMsg), closeChecker),
   218  				}
   219  			},
   220  		})
   221  
   222  	return svc
   223  }
   224  
   225  // funcCloser converts io.Reader into io.ReadCloser by adding a custom function that is called on Close()
   226  type funcCloser struct {
   227  	io.Reader
   228  	closeFn func() error
   229  }
   230  
   231  func newFuncCloser(reader io.Reader, closeFn func() error) *funcCloser {
   232  	return &funcCloser{Reader: reader, closeFn: closeFn}
   233  }
   234  
   235  func (f funcCloser) Close() error {
   236  	return f.closeFn()
   237  }
   238  
   239  func TestStatusOKPayloadHandling(t *testing.T) {
   240  	cases := map[string]struct {
   241  		Header   http.Header
   242  		Payloads [][]byte
   243  		OpCall   func(*s3.S3) error
   244  		Err      string
   245  	}{
   246  		"200 error": {
   247  			Header: http.Header{
   248  				"Content-Length": []string{strconv.Itoa(len(errMsg))},
   249  			},
   250  			Payloads: [][]byte{[]byte(errMsg)},
   251  			OpCall: func(c *s3.S3) error {
   252  				_, err := c.CopyObject(&s3.CopyObjectInput{
   253  					Bucket:     aws.String("bucketname"),
   254  					CopySource: aws.String("bucketname/doesnotexist.txt"),
   255  					Key:        aws.String("destination.txt"),
   256  				})
   257  				return err
   258  			},
   259  			Err: "ErrorCode: message body",
   260  		},
   261  		"200 error partial response": {
   262  			Header: http.Header{
   263  				"Content-Length": []string{strconv.Itoa(len(errMsg))},
   264  			},
   265  			Payloads: [][]byte{
   266  				[]byte(errMsg[:20]),
   267  			},
   268  			OpCall: func(c *s3.S3) error {
   269  				_, err := c.CopyObject(&s3.CopyObjectInput{
   270  					Bucket:     aws.String("bucketname"),
   271  					CopySource: aws.String("bucketname/doesnotexist.txt"),
   272  					Key:        aws.String("destination.txt"),
   273  				})
   274  				return err
   275  			},
   276  			Err: "unexpected EOF",
   277  		},
   278  		"200 error multipart": {
   279  			Header: http.Header{
   280  				"Transfer-Encoding": []string{"chunked"},
   281  			},
   282  			Payloads: [][]byte{
   283  				[]byte(errMsg[:20]),
   284  				[]byte(errMsg[20:]),
   285  			},
   286  			OpCall: func(c *s3.S3) error {
   287  				_, err := c.CopyObject(&s3.CopyObjectInput{
   288  					Bucket:     aws.String("bucketname"),
   289  					CopySource: aws.String("bucketname/doesnotexist.txt"),
   290  					Key:        aws.String("destination.txt"),
   291  				})
   292  				return err
   293  			},
   294  			Err: "ErrorCode: message body",
   295  		},
   296  		"200 error multipart partial response": {
   297  			Header: http.Header{
   298  				"Transfer-Encoding": []string{"chunked"},
   299  			},
   300  			Payloads: [][]byte{
   301  				[]byte(errMsg[:20]),
   302  			},
   303  			OpCall: func(c *s3.S3) error {
   304  				_, err := c.CopyObject(&s3.CopyObjectInput{
   305  					Bucket:     aws.String("bucketname"),
   306  					CopySource: aws.String("bucketname/doesnotexist.txt"),
   307  					Key:        aws.String("destination.txt"),
   308  				})
   309  				return err
   310  			},
   311  			Err: "XML syntax error",
   312  		},
   313  		"200 error multipart no payload": {
   314  			Header: http.Header{
   315  				"Transfer-Encoding": []string{"chunked"},
   316  			},
   317  			Payloads: [][]byte{},
   318  			OpCall: func(c *s3.S3) error {
   319  				_, err := c.CopyObject(&s3.CopyObjectInput{
   320  					Bucket:     aws.String("bucketname"),
   321  					CopySource: aws.String("bucketname/doesnotexist.txt"),
   322  					Key:        aws.String("destination.txt"),
   323  				})
   324  				return err
   325  			},
   326  			Err: "empty response payload",
   327  		},
   328  		"response with only xml preamble": {
   329  			Header: http.Header{
   330  				"Transfer-Encoding": []string{"chunked"},
   331  			},
   332  			Payloads: [][]byte{
   333  				[]byte(xmlPreambleMsg),
   334  			},
   335  			OpCall: func(c *s3.S3) error {
   336  				_, err := c.CopyObject(&s3.CopyObjectInput{
   337  					Bucket:     aws.String("bucketname"),
   338  					CopySource: aws.String("bucketname/doesnotexist.txt"),
   339  					Key:        aws.String("destination.txt"),
   340  				})
   341  				return err
   342  			},
   343  			Err: "empty response payload",
   344  		},
   345  	}
   346  
   347  	for name, c := range cases {
   348  		t.Run(name, func(t *testing.T) {
   349  			srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   350  				ww := w.(interface {
   351  					http.ResponseWriter
   352  					http.Flusher
   353  				})
   354  
   355  				for k, vs := range c.Header {
   356  					for _, v := range vs {
   357  						ww.Header().Add(k, v)
   358  					}
   359  				}
   360  				ww.WriteHeader(http.StatusOK)
   361  				ww.Flush()
   362  
   363  				for _, p := range c.Payloads {
   364  					ww.Write(p)
   365  					ww.Flush()
   366  				}
   367  			}))
   368  			defer srv.Close()
   369  
   370  			client := s3.New(unit.Session, &aws.Config{
   371  				Endpoint:               &srv.URL,
   372  				DisableSSL:             aws.Bool(true),
   373  				DisableParamValidation: aws.Bool(true),
   374  				S3ForcePathStyle:       aws.Bool(true),
   375  			})
   376  
   377  			err := c.OpCall(client)
   378  			if len(c.Err) != 0 {
   379  				if err == nil {
   380  					t.Fatalf("expect error, got none")
   381  				}
   382  				if e, a := c.Err, err.Error(); !strings.Contains(a, e) {
   383  					t.Fatalf("expect %v error in, %v", e, a)
   384  				}
   385  			} else if err != nil {
   386  				t.Fatalf("expect no error, got %v", err)
   387  			}
   388  		})
   389  	}
   390  }