8784: Fix test for latest firefox.
[arvados.git] / sdk / go / arvadosclient / arvadosclient_test.go
1 package arvadosclient
2
3 import (
4         "fmt"
5         "net"
6         "net/http"
7         "os"
8         "testing"
9         "time"
10
11         "git.curoverse.com/arvados.git/sdk/go/arvadostest"
12         . "gopkg.in/check.v1"
13 )
14
15 // Gocheck boilerplate
16 func Test(t *testing.T) {
17         TestingT(t)
18 }
19
20 var _ = Suite(&ServerRequiredSuite{})
21 var _ = Suite(&UnitSuite{})
22 var _ = Suite(&MockArvadosServerSuite{})
23
24 // Tests that require the Keep server running
25 type ServerRequiredSuite struct{}
26
27 func (s *ServerRequiredSuite) SetUpSuite(c *C) {
28         arvadostest.StartAPI()
29         arvadostest.StartKeep(2, false)
30         RetryDelay = 0
31 }
32
33 func (s *ServerRequiredSuite) TearDownSuite(c *C) {
34         arvadostest.StopKeep(2)
35         arvadostest.StopAPI()
36 }
37
38 func (s *ServerRequiredSuite) SetUpTest(c *C) {
39         arvadostest.ResetEnv()
40 }
41
42 func (s *ServerRequiredSuite) TestMakeArvadosClientSecure(c *C) {
43         os.Setenv("ARVADOS_API_HOST_INSECURE", "")
44         ac, err := MakeArvadosClient()
45         c.Assert(err, Equals, nil)
46         c.Check(ac.ApiServer, Equals, os.Getenv("ARVADOS_API_HOST"))
47         c.Check(ac.ApiToken, Equals, os.Getenv("ARVADOS_API_TOKEN"))
48         c.Check(ac.ApiInsecure, Equals, false)
49 }
50
51 func (s *ServerRequiredSuite) TestMakeArvadosClientInsecure(c *C) {
52         os.Setenv("ARVADOS_API_HOST_INSECURE", "true")
53         ac, err := MakeArvadosClient()
54         c.Assert(err, Equals, nil)
55         c.Check(ac.ApiInsecure, Equals, true)
56         c.Check(ac.ApiServer, Equals, os.Getenv("ARVADOS_API_HOST"))
57         c.Check(ac.ApiToken, Equals, os.Getenv("ARVADOS_API_TOKEN"))
58         c.Check(ac.Client.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify, Equals, true)
59 }
60
61 func (s *ServerRequiredSuite) TestGetInvalidUUID(c *C) {
62         arv, err := MakeArvadosClient()
63
64         getback := make(Dict)
65         err = arv.Get("collections", "", nil, &getback)
66         c.Assert(err, Equals, ErrInvalidArgument)
67         c.Assert(len(getback), Equals, 0)
68
69         err = arv.Get("collections", "zebra-moose-unicorn", nil, &getback)
70         c.Assert(err, Equals, ErrInvalidArgument)
71         c.Assert(len(getback), Equals, 0)
72
73         err = arv.Get("collections", "acbd18db4cc2f85cedef654fccc4a4d8", nil, &getback)
74         c.Assert(err, Equals, ErrInvalidArgument)
75         c.Assert(len(getback), Equals, 0)
76 }
77
78 func (s *ServerRequiredSuite) TestGetValidUUID(c *C) {
79         arv, err := MakeArvadosClient()
80
81         getback := make(Dict)
82         err = arv.Get("collections", "zzzzz-4zz18-abcdeabcdeabcde", nil, &getback)
83         c.Assert(err, FitsTypeOf, APIServerError{})
84         c.Assert(err.(APIServerError).HttpStatusCode, Equals, http.StatusNotFound)
85         c.Assert(len(getback), Equals, 0)
86
87         err = arv.Get("collections", "acbd18db4cc2f85cedef654fccc4a4d8+3", nil, &getback)
88         c.Assert(err, FitsTypeOf, APIServerError{})
89         c.Assert(err.(APIServerError).HttpStatusCode, Equals, http.StatusNotFound)
90         c.Assert(len(getback), Equals, 0)
91 }
92
93 func (s *ServerRequiredSuite) TestInvalidResourceType(c *C) {
94         arv, err := MakeArvadosClient()
95
96         getback := make(Dict)
97         err = arv.Get("unicorns", "zzzzz-zebra-unicorn7unicorn", nil, &getback)
98         c.Assert(err, FitsTypeOf, APIServerError{})
99         c.Assert(err.(APIServerError).HttpStatusCode, Equals, http.StatusNotFound)
100         c.Assert(len(getback), Equals, 0)
101
102         err = arv.Update("unicorns", "zzzzz-zebra-unicorn7unicorn", nil, &getback)
103         c.Assert(err, FitsTypeOf, APIServerError{})
104         c.Assert(err.(APIServerError).HttpStatusCode, Equals, http.StatusNotFound)
105         c.Assert(len(getback), Equals, 0)
106
107         err = arv.List("unicorns", nil, &getback)
108         c.Assert(err, FitsTypeOf, APIServerError{})
109         c.Assert(err.(APIServerError).HttpStatusCode, Equals, http.StatusNotFound)
110         c.Assert(len(getback), Equals, 0)
111 }
112
113 func (s *ServerRequiredSuite) TestCreatePipelineTemplate(c *C) {
114         arv, err := MakeArvadosClient()
115
116         for _, idleConnections := range []bool{
117                 false,
118                 true,
119         } {
120                 if idleConnections {
121                         arv.lastClosedIdlesAt = time.Now().Add(-time.Minute)
122                 } else {
123                         arv.lastClosedIdlesAt = time.Now()
124                 }
125
126                 getback := make(Dict)
127                 err = arv.Create("pipeline_templates",
128                         Dict{"pipeline_template": Dict{
129                                 "name": "tmp",
130                                 "components": Dict{
131                                         "c1": map[string]string{"script": "script1"},
132                                         "c2": map[string]string{"script": "script2"}}}},
133                         &getback)
134                 c.Assert(err, Equals, nil)
135                 c.Assert(getback["name"], Equals, "tmp")
136                 c.Assert(getback["components"].(map[string]interface{})["c2"].(map[string]interface{})["script"], Equals, "script2")
137
138                 uuid := getback["uuid"].(string)
139
140                 getback = make(Dict)
141                 err = arv.Get("pipeline_templates", uuid, nil, &getback)
142                 c.Assert(err, Equals, nil)
143                 c.Assert(getback["name"], Equals, "tmp")
144                 c.Assert(getback["components"].(map[string]interface{})["c1"].(map[string]interface{})["script"], Equals, "script1")
145
146                 getback = make(Dict)
147                 err = arv.Update("pipeline_templates", uuid,
148                         Dict{
149                                 "pipeline_template": Dict{"name": "tmp2"}},
150                         &getback)
151                 c.Assert(err, Equals, nil)
152                 c.Assert(getback["name"], Equals, "tmp2")
153
154                 c.Assert(getback["uuid"].(string), Equals, uuid)
155                 getback = make(Dict)
156                 err = arv.Delete("pipeline_templates", uuid, nil, &getback)
157                 c.Assert(err, Equals, nil)
158                 c.Assert(getback["name"], Equals, "tmp2")
159         }
160 }
161
162 func (s *ServerRequiredSuite) TestErrorResponse(c *C) {
163         arv, _ := MakeArvadosClient()
164
165         getback := make(Dict)
166
167         {
168                 err := arv.Create("logs",
169                         Dict{"log": Dict{"bogus_attr": "foo"}},
170                         &getback)
171                 c.Assert(err, ErrorMatches, "arvados API server error: .*")
172                 c.Assert(err, ErrorMatches, ".*unknown attribute(: | ')bogus_attr.*")
173                 c.Assert(err, FitsTypeOf, APIServerError{})
174                 c.Assert(err.(APIServerError).HttpStatusCode, Equals, 422)
175         }
176
177         {
178                 err := arv.Create("bogus",
179                         Dict{"bogus": Dict{}},
180                         &getback)
181                 c.Assert(err, ErrorMatches, "arvados API server error: .*")
182                 c.Assert(err, ErrorMatches, ".*Path not found.*")
183                 c.Assert(err, FitsTypeOf, APIServerError{})
184                 c.Assert(err.(APIServerError).HttpStatusCode, Equals, 404)
185         }
186 }
187
188 func (s *ServerRequiredSuite) TestAPIDiscovery_Get_defaultCollectionReplication(c *C) {
189         arv, err := MakeArvadosClient()
190         value, err := arv.Discovery("defaultCollectionReplication")
191         c.Assert(err, IsNil)
192         c.Assert(value, NotNil)
193 }
194
195 func (s *ServerRequiredSuite) TestAPIDiscovery_Get_noSuchParameter(c *C) {
196         arv, err := MakeArvadosClient()
197         value, err := arv.Discovery("noSuchParameter")
198         c.Assert(err, NotNil)
199         c.Assert(value, IsNil)
200 }
201
202 type UnitSuite struct{}
203
204 func (s *UnitSuite) TestUUIDMatch(c *C) {
205         c.Assert(UUIDMatch("zzzzz-tpzed-000000000000000"), Equals, true)
206         c.Assert(UUIDMatch("zzzzz-zebra-000000000000000"), Equals, true)
207         c.Assert(UUIDMatch("00000-00000-zzzzzzzzzzzzzzz"), Equals, true)
208         c.Assert(UUIDMatch("ZEBRA-HORSE-AFRICANELEPHANT"), Equals, false)
209         c.Assert(UUIDMatch(" zzzzz-tpzed-000000000000000"), Equals, false)
210         c.Assert(UUIDMatch("d41d8cd98f00b204e9800998ecf8427e"), Equals, false)
211         c.Assert(UUIDMatch("d41d8cd98f00b204e9800998ecf8427e+0"), Equals, false)
212         c.Assert(UUIDMatch(""), Equals, false)
213 }
214
215 func (s *UnitSuite) TestPDHMatch(c *C) {
216         c.Assert(PDHMatch("zzzzz-tpzed-000000000000000"), Equals, false)
217         c.Assert(PDHMatch("d41d8cd98f00b204e9800998ecf8427e"), Equals, false)
218         c.Assert(PDHMatch("d41d8cd98f00b204e9800998ecf8427e+0"), Equals, true)
219         c.Assert(PDHMatch("d41d8cd98f00b204e9800998ecf8427e+12345"), Equals, true)
220         c.Assert(PDHMatch("d41d8cd98f00b204e9800998ecf8427e 12345"), Equals, false)
221         c.Assert(PDHMatch("D41D8CD98F00B204E9800998ECF8427E+12345"), Equals, false)
222         c.Assert(PDHMatch("d41d8cd98f00b204e9800998ecf8427e+12345 "), Equals, false)
223         c.Assert(PDHMatch("d41d8cd98f00b204e9800998ecf8427e+abcdef"), Equals, false)
224         c.Assert(PDHMatch("da39a3ee5e6b4b0d3255bfef95601890afd80709"), Equals, false)
225         c.Assert(PDHMatch("da39a3ee5e6b4b0d3255bfef95601890afd80709+0"), Equals, false)
226         c.Assert(PDHMatch("d41d8cd98f00b204e9800998ecf8427+12345"), Equals, false)
227         c.Assert(PDHMatch("d41d8cd98f00b204e9800998ecf8427e+12345\n"), Equals, false)
228         c.Assert(PDHMatch("+12345"), Equals, false)
229         c.Assert(PDHMatch(""), Equals, false)
230 }
231
232 // Tests that use mock arvados server
233 type MockArvadosServerSuite struct{}
234
235 func (s *MockArvadosServerSuite) SetUpSuite(c *C) {
236         RetryDelay = 0
237 }
238
239 func (s *MockArvadosServerSuite) SetUpTest(c *C) {
240         arvadostest.ResetEnv()
241 }
242
243 type APIServer struct {
244         listener net.Listener
245         url      string
246 }
247
248 func RunFakeArvadosServer(st http.Handler) (api APIServer, err error) {
249         api.listener, err = net.ListenTCP("tcp", &net.TCPAddr{Port: 0})
250         if err != nil {
251                 return
252         }
253         api.url = api.listener.Addr().String()
254         go http.Serve(api.listener, st)
255         return
256 }
257
258 type APIStub struct {
259         method        string
260         retryAttempts int
261         expected      int
262         respStatus    []int
263         responseBody  []string
264 }
265
266 func (h *APIStub) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
267         if req.URL.Path == "/redirect-loop" {
268                 http.Redirect(resp, req, "/redirect-loop", http.StatusFound)
269                 return
270         }
271         if h.respStatus[h.retryAttempts] < 0 {
272                 // Fail the client's Do() by starting a redirect loop
273                 http.Redirect(resp, req, "/redirect-loop", http.StatusFound)
274         } else {
275                 resp.WriteHeader(h.respStatus[h.retryAttempts])
276                 resp.Write([]byte(h.responseBody[h.retryAttempts]))
277         }
278         h.retryAttempts++
279 }
280
281 func (s *MockArvadosServerSuite) TestWithRetries(c *C) {
282         for _, stub := range []APIStub{
283                 {
284                         "get", 0, 200, []int{200, 500}, []string{`{"ok":"ok"}`, ``},
285                 },
286                 {
287                         "create", 0, 200, []int{200, 500}, []string{`{"ok":"ok"}`, ``},
288                 },
289                 {
290                         "get", 0, 500, []int{500, 500, 500, 200}, []string{``, ``, ``, `{"ok":"ok"}`},
291                 },
292                 {
293                         "create", 0, 500, []int{500, 500, 500, 200}, []string{``, ``, ``, `{"ok":"ok"}`},
294                 },
295                 {
296                         "update", 0, 500, []int{500, 500, 500, 200}, []string{``, ``, ``, `{"ok":"ok"}`},
297                 },
298                 {
299                         "delete", 0, 500, []int{500, 500, 500, 200}, []string{``, ``, ``, `{"ok":"ok"}`},
300                 },
301                 {
302                         "get", 0, 502, []int{500, 500, 502, 200}, []string{``, ``, ``, `{"ok":"ok"}`},
303                 },
304                 {
305                         "create", 0, 502, []int{500, 500, 502, 200}, []string{``, ``, ``, `{"ok":"ok"}`},
306                 },
307                 {
308                         "get", 0, 200, []int{500, 500, 200}, []string{``, ``, `{"ok":"ok"}`},
309                 },
310                 {
311                         "create", 0, 200, []int{500, 500, 200}, []string{``, ``, `{"ok":"ok"}`},
312                 },
313                 {
314                         "delete", 0, 200, []int{500, 500, 200}, []string{``, ``, `{"ok":"ok"}`},
315                 },
316                 {
317                         "update", 0, 200, []int{500, 500, 200}, []string{``, ``, `{"ok":"ok"}`},
318                 },
319                 {
320                         "get", 0, 401, []int{401, 200}, []string{``, `{"ok":"ok"}`},
321                 },
322                 {
323                         "create", 0, 401, []int{401, 200}, []string{``, `{"ok":"ok"}`},
324                 },
325                 {
326                         "get", 0, 404, []int{404, 200}, []string{``, `{"ok":"ok"}`},
327                 },
328                 {
329                         "get", 0, 401, []int{500, 401, 200}, []string{``, ``, `{"ok":"ok"}`},
330                 },
331
332                 // Response code -1 simulates an HTTP/network error
333                 // (i.e., Do() returns an error; there is no HTTP
334                 // response status code).
335
336                 // Succeed on second retry
337                 {
338                         "get", 0, 200, []int{-1, -1, 200}, []string{``, ``, `{"ok":"ok"}`},
339                 },
340                 // "POST" is not safe to retry: fail after one error
341                 {
342                         "create", 0, -1, []int{-1, 200}, []string{``, `{"ok":"ok"}`},
343                 },
344         } {
345                 api, err := RunFakeArvadosServer(&stub)
346                 c.Check(err, IsNil)
347
348                 defer api.listener.Close()
349
350                 arv := ArvadosClient{
351                         Scheme:      "http",
352                         ApiServer:   api.url,
353                         ApiToken:    "abc123",
354                         ApiInsecure: true,
355                         Client:      &http.Client{Transport: &http.Transport{}},
356                         Retries:     2}
357
358                 getback := make(Dict)
359                 switch stub.method {
360                 case "get":
361                         err = arv.Get("collections", "zzzzz-4zz18-znfnqtbbv4spc3w", nil, &getback)
362                 case "create":
363                         err = arv.Create("collections",
364                                 Dict{"collection": Dict{"name": "testing"}},
365                                 &getback)
366                 case "update":
367                         err = arv.Update("collections", "zzzzz-4zz18-znfnqtbbv4spc3w",
368                                 Dict{"collection": Dict{"name": "testing"}},
369                                 &getback)
370                 case "delete":
371                         err = arv.Delete("pipeline_templates", "zzzzz-4zz18-znfnqtbbv4spc3w", nil, &getback)
372                 }
373
374                 switch stub.expected {
375                 case 200:
376                         c.Check(err, IsNil)
377                         c.Check(getback["ok"], Equals, "ok")
378                 case -1:
379                         c.Check(err, NotNil)
380                         c.Check(err, ErrorMatches, `.*stopped after \d+ redirects`)
381                 default:
382                         c.Check(err, NotNil)
383                         c.Check(err, ErrorMatches, fmt.Sprintf("arvados API server error: %d.*", stub.expected))
384                         c.Check(err.(APIServerError).HttpStatusCode, Equals, stub.expected)
385                 }
386         }
387 }