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 }