go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cipd/appengine/impl/gs/uploader_test.go (about)

     1  // Copyright 2018 The LUCI 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  //      http://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 gs
    16  
    17  import (
    18  	"context"
    19  	"io"
    20  	"net/http"
    21  	"net/http/httptest"
    22  	"testing"
    23  	"time"
    24  
    25  	"go.chromium.org/luci/common/clock"
    26  	"go.chromium.org/luci/common/clock/testclock"
    27  
    28  	. "github.com/smartystreets/goconvey/convey"
    29  )
    30  
    31  func TestUploader(t *testing.T) {
    32  	t.Parallel()
    33  
    34  	Convey("With mocks", t, func(c C) {
    35  		ctx, cl := testclock.UseTime(context.Background(), testclock.TestRecentTimeUTC)
    36  		cl.SetTimerCallback(func(d time.Duration, t clock.Timer) { cl.Add(d) })
    37  
    38  		type call struct {
    39  			ContentRange string // expected Content-Range request header
    40  			Body         []byte // expected request body
    41  
    42  			Code  int    // HTTP response code (required)
    43  			Range string // Range response header
    44  			Err   error  // error to return instead of a response
    45  		}
    46  
    47  		expected := []call{}
    48  		expect := func(c call) { expected = append(expected, c) }
    49  
    50  		requestMock := func(r *http.Request) (*http.Response, error) {
    51  			if len(expected) == 0 {
    52  				t.Fatalf("unexpected call with Content-Range %q", r.Header.Get("Content-Range"))
    53  			}
    54  			next := expected[0]
    55  			expected = expected[1:]
    56  
    57  			c.So(r.Method, ShouldEqual, "PUT")
    58  			c.So(r.URL.Path, ShouldEqual, "/file")
    59  			c.So(r.Header.Get("Content-Range"), ShouldEqual, next.ContentRange)
    60  
    61  			body, err := io.ReadAll(r.Body)
    62  			So(err, ShouldBeNil)
    63  			if len(body) == 0 {
    64  				body = nil
    65  			}
    66  			So(body, ShouldResemble, next.Body)
    67  
    68  			if next.Err != nil {
    69  				return nil, next.Err
    70  			}
    71  
    72  			resp := httptest.NewRecorder()
    73  			if next.Range != "" {
    74  				resp.Header().Set("Range", next.Range)
    75  			}
    76  			resp.WriteHeader(next.Code)
    77  			return resp.Result(), nil
    78  		}
    79  
    80  		upl := Uploader{
    81  			Context:     ctx,
    82  			UploadURL:   "http://example.com/file",
    83  			FileSize:    5,
    84  			requestMock: requestMock,
    85  		}
    86  
    87  		Convey("Happy path", func() {
    88  			expect(call{
    89  				ContentRange: "bytes 0-2/5",
    90  				Body:         []uint8{0, 1, 2},
    91  				Code:         308,
    92  				Range:        "bytes=0-2",
    93  			})
    94  			n, err := upl.Write([]byte{0, 1, 2})
    95  			So(err, ShouldBeNil)
    96  			So(n, ShouldEqual, 3)
    97  
    98  			expect(call{
    99  				ContentRange: "bytes 3-4/5",
   100  				Body:         []uint8{3, 4},
   101  				Code:         201,
   102  			})
   103  			n, err = upl.Write([]byte{3, 4})
   104  			So(err, ShouldBeNil)
   105  			So(n, ShouldEqual, 2)
   106  		})
   107  
   108  		Convey("Restarts successfully", func() {
   109  			expect(call{
   110  				ContentRange: "bytes 0-2/5",
   111  				Body:         []uint8{0, 1, 2},
   112  				Code:         308,
   113  				Range:        "bytes=0-2",
   114  			})
   115  			n, err := upl.Write([]byte{0, 1, 2})
   116  			So(err, ShouldBeNil)
   117  			So(n, ShouldEqual, 3)
   118  
   119  			// Tries to upload, fails, resumes, fails during the resuming, retries,
   120  			// succeeds.
   121  			expect(call{
   122  				ContentRange: "bytes 3-4/5",
   123  				Body:         []uint8{3, 4},
   124  				Code:         500,
   125  			})
   126  			expect(call{
   127  				ContentRange: "bytes */5",
   128  				Code:         500,
   129  			})
   130  			expect(call{
   131  				ContentRange: "bytes */5",
   132  				Code:         308,
   133  				Range:        "bytes=0-2",
   134  			})
   135  			expect(call{
   136  				ContentRange: "bytes 3-4/5",
   137  				Body:         []uint8{3, 4},
   138  				Code:         201,
   139  			})
   140  			n, err = upl.Write([]byte{3, 4})
   141  			So(err, ShouldBeNil)
   142  			So(n, ShouldEqual, 2)
   143  		})
   144  
   145  		Convey("Restarting on first write", func() {
   146  			expect(call{
   147  				ContentRange: "bytes 0-2/5",
   148  				Body:         []uint8{0, 1, 2},
   149  				Code:         500,
   150  			})
   151  			expect(call{
   152  				ContentRange: "bytes */5",
   153  				Code:         308,
   154  				// Note: no Range header here
   155  			})
   156  			expect(call{
   157  				ContentRange: "bytes 0-2/5",
   158  				Body:         []uint8{0, 1, 2},
   159  				Code:         308,
   160  				Range:        "bytes=0-2",
   161  			})
   162  			n, err := upl.Write([]byte{0, 1, 2})
   163  			So(err, ShouldBeNil)
   164  			So(n, ShouldEqual, 3)
   165  		})
   166  
   167  		Convey("Restarting with far away offset", func() {
   168  			expect(call{
   169  				ContentRange: "bytes 0-2/5",
   170  				Body:         []uint8{0, 1, 2},
   171  				Code:         308,
   172  				Range:        "bytes=0-2",
   173  			})
   174  			n, err := upl.Write([]byte{0, 1, 2})
   175  			So(err, ShouldBeNil)
   176  			So(n, ShouldEqual, 3)
   177  
   178  			expect(call{
   179  				ContentRange: "bytes 3-4/5",
   180  				Body:         []uint8{3, 4},
   181  				Code:         500,
   182  			})
   183  			expect(call{
   184  				ContentRange: "bytes */5",
   185  				Code:         308,
   186  				Range:        "bytes=0-1",
   187  			})
   188  			n, err = upl.Write([]byte{3, 4})
   189  			So(err, ShouldResemble, &RestartUploadError{Offset: 2})
   190  			So(n, ShouldEqual, 0)
   191  		})
   192  	})
   193  }