OLD | NEW |
| (Empty) |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package cipd | |
6 | |
7 import ( | |
8 "bytes" | |
9 "fmt" | |
10 "io/ioutil" | |
11 "net/http" | |
12 "net/url" | |
13 "testing" | |
14 "time" | |
15 | |
16 "infra/libs/logging" | |
17 | |
18 . "github.com/smartystreets/goconvey/convey" | |
19 ) | |
20 | |
21 func TestUploadToCAS(t *testing.T) { | |
22 Convey("Given a mocked service and clock", t, func() { | |
23 mockClock(time.Now()) | |
24 mockResumableUpload() | |
25 | |
26 Convey("UploadToCAS full flow", func() { | |
27 mockRemoteServiceWithExpectations([]expectedHTTPCall{ | |
28 { | |
29 Method: "POST", | |
30 Path: "/_ah/api/cas/v1/upload/SHA1/abc
", | |
31 Reply: `{"status":"SUCCESS","upload_ses
sion_id":"12345","upload_url":"http://localhost"}`, | |
32 }, | |
33 { | |
34 Method: "POST", | |
35 Path: "/_ah/api/cas/v1/finalize/12345"
, | |
36 Reply: `{"status":"VERIFYING"}`, | |
37 }, | |
38 { | |
39 Method: "POST", | |
40 Path: "/_ah/api/cas/v1/finalize/12345"
, | |
41 Reply: `{"status":"PUBLISHED"}`, | |
42 }, | |
43 }) | |
44 err := UploadToCAS(UploadToCASOptions{SHA1: "abc"}) | |
45 So(err, ShouldBeNil) | |
46 }) | |
47 | |
48 Convey("UploadToCAS timeout", func() { | |
49 // Append a bunch of "still verifying" responses at the
end. | |
50 calls := []expectedHTTPCall{ | |
51 { | |
52 Method: "POST", | |
53 Path: "/_ah/api/cas/v1/upload/SHA1/abc
", | |
54 Reply: `{"status":"SUCCESS","upload_ses
sion_id":"12345","upload_url":"http://localhost"}`, | |
55 }, | |
56 } | |
57 for i := 0; i < 19; i++ { | |
58 calls = append(calls, expectedHTTPCall{ | |
59 Method: "POST", | |
60 Path: "/_ah/api/cas/v1/finalize/12345"
, | |
61 Reply: `{"status":"VERIFYING"}`, | |
62 }) | |
63 } | |
64 mockRemoteServiceWithExpectations(calls) | |
65 err := UploadToCAS(UploadToCASOptions{SHA1: "abc"}) | |
66 So(err, ShouldEqual, ErrFinalizationTimeout) | |
67 }) | |
68 }) | |
69 } | |
70 | |
71 func TestRegisterInstance(t *testing.T) { | |
72 Convey("Mocking remote service", t, func() { | |
73 mockClock(time.Now()) | |
74 mockResumableUpload() | |
75 | |
76 // Build an empty package to be uploaded. | |
77 out := bytes.Buffer{} | |
78 err := BuildInstance(BuildInstanceOptions{ | |
79 Input: []File{}, | |
80 Output: &out, | |
81 PackageName: "testing", | |
82 }) | |
83 So(err, ShouldBeNil) | |
84 | |
85 // Open it for reading. | |
86 inst, err := OpenInstance(bytes.NewReader(out.Bytes()), "") | |
87 So(err, ShouldBeNil) | |
88 Reset(func() { inst.Close() }) | |
89 | |
90 Convey("RegisterInstance full flow", func() { | |
91 mockRemoteServiceWithExpectations([]expectedHTTPCall{ | |
92 { | |
93 Method: "POST", | |
94 Path: "/_ah/api/repo/v1/instance", | |
95 Query: url.Values{ | |
96 "instance_id": []string{inst.In
stanceID()}, | |
97 "package_name": []string{inst.Pa
ckageName()}, | |
98 }, | |
99 Reply: `{ | |
100 "status": "UPLOAD_FIRST", | |
101 "upload_session_id": "12345", | |
102 "upload_url": "http://localhost" | |
103 }`, | |
104 }, | |
105 { | |
106 Method: "POST", | |
107 Path: "/_ah/api/cas/v1/finalize/12345"
, | |
108 Reply: `{"status":"PUBLISHED"}`, | |
109 }, | |
110 { | |
111 Method: "POST", | |
112 Path: "/_ah/api/repo/v1/instance", | |
113 Query: url.Values{ | |
114 "instance_id": []string{inst.In
stanceID()}, | |
115 "package_name": []string{inst.Pa
ckageName()}, | |
116 }, | |
117 Reply: `{ | |
118 "status": "REGISTERED", | |
119 "instance": { | |
120 "registered_by": "user:a
@example.com", | |
121 "registered_ts": "0" | |
122 } | |
123 }`, | |
124 }, | |
125 }) | |
126 err = RegisterInstance(RegisterInstanceOptions{PackageIn
stance: inst}) | |
127 So(err, ShouldBeNil) | |
128 }) | |
129 | |
130 Convey("RegisterInstance already registered", func() { | |
131 mockRemoteServiceWithExpectations([]expectedHTTPCall{ | |
132 { | |
133 Method: "POST", | |
134 Path: "/_ah/api/repo/v1/instance", | |
135 Query: url.Values{ | |
136 "instance_id": []string{inst.In
stanceID()}, | |
137 "package_name": []string{inst.Pa
ckageName()}, | |
138 }, | |
139 Reply: `{ | |
140 "status": "ALREADY_REGISTERED", | |
141 "instance": { | |
142 "registered_by": "user:a
@example.com", | |
143 "registered_ts": "0" | |
144 } | |
145 }`, | |
146 }, | |
147 }) | |
148 err = RegisterInstance(RegisterInstanceOptions{PackageIn
stance: inst}) | |
149 So(err, ShouldBeNil) | |
150 }) | |
151 }) | |
152 } | |
153 | |
154 func TestResumableUpload(t *testing.T) { | |
155 Convey("Resumable upload full flow", t, func(c C) { | |
156 mockClock(time.Now()) | |
157 | |
158 dataToUpload := "0123456789abcdef" | |
159 totalLen := len(dataToUpload) | |
160 uploaded := bytes.NewBuffer(nil) | |
161 errors := 0 | |
162 | |
163 server, client := mockServerWithHandler("/", func(w http.Respons
eWriter, r *http.Request) { | |
164 c.So(r.URL.Path, ShouldEqual, "/upl") | |
165 c.So(r.Method, ShouldEqual, "PUT") | |
166 | |
167 rangeHeader := r.Header.Get("Content-Range") | |
168 body, err := ioutil.ReadAll(r.Body) | |
169 c.So(err, ShouldBeNil) | |
170 | |
171 // Insert a bunch of consecutive transient errors in the
middle. | |
172 cur := uploaded.Len() | |
173 if cur > totalLen/2 && errors < 3 { | |
174 errors++ | |
175 w.WriteHeader(500) | |
176 return | |
177 } | |
178 | |
179 // Request for uploaded offset. | |
180 if len(body) == 0 { | |
181 c.So(rangeHeader, ShouldEqual, fmt.Sprintf("byte
s */%d", totalLen)) | |
182 if cur == totalLen { | |
183 w.WriteHeader(200) | |
184 return | |
185 } | |
186 if cur != 0 { | |
187 w.Header().Set("Range", fmt.Sprintf("byt
es=0-%d", cur-1)) | |
188 } | |
189 w.WriteHeader(308) | |
190 return | |
191 } | |
192 | |
193 // Uploading next chunk. | |
194 c.So(rangeHeader, ShouldEqual, fmt.Sprintf("bytes %d-%d/
%d", cur, cur+len(body)-1, totalLen)) | |
195 _, err = uploaded.Write(body) | |
196 c.So(err, ShouldBeNil) | |
197 if uploaded.Len() == totalLen { | |
198 w.WriteHeader(200) | |
199 } else { | |
200 w.WriteHeader(308) | |
201 } | |
202 }) | |
203 | |
204 err := resumableUpload(server.URL+"/upl", 3, UploadToCASOptions{ | |
205 SHA1: "abc", | |
206 Data: bytes.NewReader([]byte(dataToUpload)), | |
207 UploadOptions: UploadOptions{ | |
208 Client: client, | |
209 Log: logging.DefaultLogger, | |
210 }, | |
211 }) | |
212 So(err, ShouldBeNil) | |
213 So(uploaded.Bytes(), ShouldResemble, []byte(dataToUpload)) | |
214 }) | |
215 } | |
216 | |
217 func TestAttachTagsWhenReady(t *testing.T) { | |
218 Convey("Mocking clock", t, func() { | |
219 mockClock(time.Now()) | |
220 | |
221 Convey("attachTagsWhenReady works", func() { | |
222 remote := mockRemoteServiceWithExpectations([]expectedHT
TPCall{ | |
223 { | |
224 Method: "POST", | |
225 Path: "/_ah/api/repo/v1/tags", | |
226 Query: url.Values{ | |
227 "instance_id": []string{"aaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, | |
228 "package_name": []string{"pkgnam
e"}, | |
229 }, | |
230 Reply: `{"status": "PROCESSING_NOT_FINIS
HED_YET"}`, | |
231 }, | |
232 { | |
233 Method: "POST", | |
234 Path: "/_ah/api/repo/v1/tags", | |
235 Query: url.Values{ | |
236 "instance_id": []string{"aaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, | |
237 "package_name": []string{"pkgnam
e"}, | |
238 }, | |
239 Reply: `{"status": "SUCCESS"}`, | |
240 }, | |
241 }) | |
242 err := attachTagsWhenReady( | |
243 remote, "pkgname", "aaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaa", | |
244 []string{"tag1:value1"}, logging.DefaultLogger) | |
245 So(err, ShouldBeNil) | |
246 }) | |
247 | |
248 Convey("attachTagsWhenReady timeout", func() { | |
249 calls := []expectedHTTPCall{} | |
250 for i := 0; i < 20; i++ { | |
251 calls = append(calls, expectedHTTPCall{ | |
252 Method: "POST", | |
253 Path: "/_ah/api/repo/v1/tags", | |
254 Query: url.Values{ | |
255 "instance_id": []string{"aaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, | |
256 "package_name": []string{"pkgnam
e"}, | |
257 }, | |
258 Reply: `{"status": "PROCESSING_NOT_FINIS
HED_YET"}`, | |
259 }) | |
260 } | |
261 remote := mockRemoteServiceWithExpectations(calls) | |
262 err := attachTagsWhenReady( | |
263 remote, "pkgname", "aaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaa", | |
264 []string{"tag1:value1"}, logging.DefaultLogger) | |
265 So(err, ShouldEqual, ErrAttachTagsTimeout) | |
266 }) | |
267 }) | |
268 } | |
269 | |
270 func mockResumableUpload() { | |
271 prev := resumableUpload | |
272 resumableUpload = func(string, int64, UploadToCASOptions) error { | |
273 return nil | |
274 } | |
275 Reset(func() { resumableUpload = prev }) | |
276 } | |
OLD | NEW |