Merge branch '9964-output-glob-acr' refs #9964
[arvados.git] / lib / controller / router / request_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package router
6
7 import (
8         "bytes"
9         "encoding/json"
10         "io"
11         "mime/multipart"
12         "net/http"
13         "net/http/httptest"
14         "net/url"
15
16         "git.arvados.org/arvados.git/sdk/go/arvados"
17         "git.arvados.org/arvados.git/sdk/go/arvadostest"
18         check "gopkg.in/check.v1"
19 )
20
21 type testReq struct {
22         method   string
23         path     string
24         token    string // default is ActiveTokenV2; use noToken to omit
25         param    map[string]interface{}
26         attrs    map[string]interface{}
27         attrsKey string
28         header   http.Header
29
30         // variations on request formatting
31         json            bool
32         jsonAttrsTop    bool
33         jsonStringParam bool
34         tokenInBody     bool
35         tokenInQuery    bool
36         noContentType   bool
37
38         body        *bytes.Buffer // provided by caller
39         bodyContent []byte        // set by (*testReq)Request() if body not provided by caller
40 }
41
42 const noToken = "(no token)"
43
44 func (tr *testReq) Request() *http.Request {
45         param := map[string]interface{}{}
46         for k, v := range tr.param {
47                 param[k] = v
48         }
49
50         var body *bytes.Buffer
51         if tr.body != nil {
52                 // caller provided a buffer
53                 body = tr.body
54         } else if tr.json {
55                 if tr.jsonAttrsTop {
56                         for k, v := range tr.attrs {
57                                 if tr.jsonStringParam {
58                                         j, err := json.Marshal(v)
59                                         if err != nil {
60                                                 panic(err)
61                                         }
62                                         param[k] = string(j)
63                                 } else {
64                                         param[k] = v
65                                 }
66                         }
67                 } else if tr.attrs != nil {
68                         if tr.jsonStringParam {
69                                 j, err := json.Marshal(tr.attrs)
70                                 if err != nil {
71                                         panic(err)
72                                 }
73                                 param[tr.attrsKey] = string(j)
74                         } else {
75                                 param[tr.attrsKey] = tr.attrs
76                         }
77                 }
78                 body = bytes.NewBuffer(nil)
79                 err := json.NewEncoder(body).Encode(param)
80                 if err != nil {
81                         panic(err)
82                 }
83                 tr.bodyContent = body.Bytes()
84         } else {
85                 values := make(url.Values)
86                 for k, v := range param {
87                         if vs, ok := v.(string); ok && !tr.jsonStringParam {
88                                 values.Set(k, vs)
89                         } else {
90                                 jv, err := json.Marshal(v)
91                                 if err != nil {
92                                         panic(err)
93                                 }
94                                 values.Set(k, string(jv))
95                         }
96                 }
97                 if tr.attrs != nil {
98                         jattrs, err := json.Marshal(tr.attrs)
99                         if err != nil {
100                                 panic(err)
101                         }
102                         values.Set(tr.attrsKey, string(jattrs))
103                 }
104                 body = bytes.NewBuffer(nil)
105                 io.WriteString(body, values.Encode())
106                 tr.bodyContent = body.Bytes()
107         }
108         method := tr.method
109         if method == "" {
110                 method = "GET"
111         }
112         path := tr.path
113         if path == "" {
114                 path = "example/test/path"
115         }
116         req := httptest.NewRequest(method, "https://an.example/"+path, body)
117         token := tr.token
118         if token == "" {
119                 token = arvadostest.ActiveTokenV2
120         }
121         if token != noToken {
122                 req.Header.Set("Authorization", "Bearer "+token)
123         }
124         if tr.json {
125                 req.Header.Set("Content-Type", "application/json")
126         } else if tr.header.Get("Content-Type") == "" {
127                 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
128         }
129         for k, v := range tr.header {
130                 req.Header[k] = append([]string(nil), v...)
131         }
132         return req
133 }
134
135 func (s *RouterSuite) TestAttrsInBody(c *check.C) {
136         attrs := map[string]interface{}{"foo": "bar"}
137
138         multipartBody := new(bytes.Buffer)
139         multipartWriter := multipart.NewWriter(multipartBody)
140         multipartWriter.WriteField("attrs", `{"foo":"bar"}`)
141         multipartWriter.Close()
142
143         for _, tr := range []testReq{
144                 {attrsKey: "model_name", json: true, attrs: attrs},
145                 {attrsKey: "model_name", json: true, attrs: attrs, jsonAttrsTop: true},
146                 {attrsKey: "model_name", json: true, attrs: attrs, jsonAttrsTop: true, jsonStringParam: true},
147                 {attrsKey: "model_name", json: true, attrs: attrs, jsonAttrsTop: false, jsonStringParam: true},
148                 {body: multipartBody, header: http.Header{"Content-Type": []string{multipartWriter.FormDataContentType()}}},
149         } {
150                 c.Logf("tr: %#v", tr)
151                 req := tr.Request()
152                 var opts struct{ Attrs struct{ Foo string } }
153                 params, err := s.rtr.loadRequestParams(req, tr.attrsKey, &opts)
154                 c.Logf("params: %#v", params)
155                 c.Assert(err, check.IsNil)
156                 c.Check(params, check.NotNil)
157                 c.Check(opts.Attrs.Foo, check.Equals, "bar")
158                 if c.Check(params["attrs"], check.FitsTypeOf, map[string]interface{}{}) {
159                         c.Check(params["attrs"].(map[string]interface{})["foo"], check.Equals, "bar")
160                 }
161         }
162 }
163
164 func (s *RouterSuite) TestBoolParam(c *check.C) {
165         testKey := "ensure_unique_name"
166
167         for i, tr := range []testReq{
168                 {method: "POST", param: map[string]interface{}{testKey: false}, json: true},
169                 {method: "POST", param: map[string]interface{}{testKey: false}},
170                 {method: "POST", param: map[string]interface{}{testKey: "false"}},
171                 {method: "POST", param: map[string]interface{}{testKey: "0"}},
172                 {method: "POST", param: map[string]interface{}{testKey: ""}},
173         } {
174                 c.Logf("#%d, tr: %#v", i, tr)
175                 req := tr.Request()
176                 c.Logf("tr.body: %s", tr.bodyContent)
177                 var opts struct{ EnsureUniqueName bool }
178                 params, err := s.rtr.loadRequestParams(req, tr.attrsKey, &opts)
179                 c.Logf("params: %#v", params)
180                 c.Assert(err, check.IsNil)
181                 c.Check(opts.EnsureUniqueName, check.Equals, false)
182                 if c.Check(params, check.NotNil) {
183                         c.Check(params[testKey], check.Equals, false)
184                 }
185         }
186
187         for i, tr := range []testReq{
188                 {method: "POST", param: map[string]interface{}{testKey: true}, json: true},
189                 {method: "POST", param: map[string]interface{}{testKey: true}},
190                 {method: "POST", param: map[string]interface{}{testKey: "true"}},
191                 {method: "POST", param: map[string]interface{}{testKey: "1"}},
192         } {
193                 c.Logf("#%d, tr: %#v", i, tr)
194                 req := tr.Request()
195                 c.Logf("tr.body: %s", tr.bodyContent)
196                 var opts struct {
197                         EnsureUniqueName bool `json:"ensure_unique_name"`
198                 }
199                 params, err := s.rtr.loadRequestParams(req, tr.attrsKey, &opts)
200                 c.Logf("params: %#v", params)
201                 c.Assert(err, check.IsNil)
202                 c.Check(opts.EnsureUniqueName, check.Equals, true)
203                 if c.Check(params, check.NotNil) {
204                         c.Check(params[testKey], check.Equals, true)
205                 }
206         }
207 }
208
209 func (s *RouterSuite) TestStringOrArrayParam(c *check.C) {
210         for _, paramname := range []string{"order", "include"} {
211                 for i, tr := range []testReq{
212                         {method: "POST", param: map[string]interface{}{paramname: ""}, json: true},
213                         {method: "POST", param: map[string]interface{}{paramname: ""}, json: false},
214                         {method: "POST", param: map[string]interface{}{paramname: []string{}}, json: true},
215                         {method: "POST", param: map[string]interface{}{paramname: []string{}}, json: false},
216                         {method: "POST", param: map[string]interface{}{}, json: true},
217                         {method: "POST", param: map[string]interface{}{}, json: false},
218                 } {
219                         c.Logf("%s #%d, tr: %#v", paramname, i, tr)
220                         req := tr.Request()
221                         c.Logf("tr.body: %s", tr.bodyContent)
222                         params, err := s.rtr.loadRequestParams(req, tr.attrsKey, nil)
223                         c.Assert(err, check.IsNil)
224                         c.Assert(params, check.NotNil)
225                         if order, ok := params[paramname]; ok && order != nil {
226                                 c.Check(order, check.DeepEquals, []interface{}{})
227                         }
228                 }
229         }
230
231         for i, tr := range []testReq{
232                 {method: "POST", param: map[string]interface{}{"order": "foo,bar desc"}, json: true},
233                 {method: "POST", param: map[string]interface{}{"order": "foo,bar desc"}, json: false},
234                 {method: "POST", param: map[string]interface{}{"order": "[\"foo\", \"bar desc\"]"}, json: false},
235                 {method: "POST", param: map[string]interface{}{"order": []string{"foo", "bar desc"}}, json: true},
236                 {method: "POST", param: map[string]interface{}{"order": []string{"foo", "bar desc"}}, json: false},
237         } {
238                 c.Logf("#%d, tr: %#v", i, tr)
239                 req := tr.Request()
240                 c.Logf("tr.body: %s", tr.bodyContent)
241                 var opts arvados.ListOptions
242                 params, err := s.rtr.loadRequestParams(req, tr.attrsKey, &opts)
243                 c.Assert(err, check.IsNil)
244                 c.Check(opts.Order, check.DeepEquals, []string{"foo", "bar desc"})
245                 if _, ok := params["order"].([]string); ok {
246                         c.Check(params["order"], check.DeepEquals, []string{"foo", "bar desc"})
247                 } else {
248                         c.Check(params["order"], check.DeepEquals, []interface{}{"foo", "bar desc"})
249                 }
250         }
251
252         for i, tr := range []testReq{
253                 {method: "POST", param: map[string]interface{}{"include": "container_uuid,owner_uuid"}, json: true},
254                 {method: "POST", param: map[string]interface{}{"include": "container_uuid,owner_uuid"}, json: false},
255                 {method: "POST", param: map[string]interface{}{"include": "[\"container_uuid\", \"owner_uuid\"]"}, json: false},
256                 {method: "POST", param: map[string]interface{}{"include": []string{"container_uuid", "owner_uuid"}}, json: true},
257                 {method: "POST", param: map[string]interface{}{"include": []string{"container_uuid", "owner_uuid"}}, json: false},
258         } {
259                 c.Logf("#%d, tr: %#v", i, tr)
260                 {
261                         req := tr.Request()
262                         c.Logf("tr.body: %s", tr.bodyContent)
263                         var opts arvados.ListOptions
264                         params, err := s.rtr.loadRequestParams(req, tr.attrsKey, &opts)
265                         c.Assert(err, check.IsNil)
266                         c.Check(opts.Include, check.DeepEquals, []string{"container_uuid", "owner_uuid"})
267                         if _, ok := params["include"].([]string); ok {
268                                 c.Check(params["include"], check.DeepEquals, []string{"container_uuid", "owner_uuid"})
269                         } else {
270                                 c.Check(params["include"], check.DeepEquals, []interface{}{"container_uuid", "owner_uuid"})
271                         }
272                 }
273                 {
274                         req := tr.Request()
275                         c.Logf("tr.body: %s", tr.bodyContent)
276                         var opts arvados.GroupContentsOptions
277                         params, err := s.rtr.loadRequestParams(req, tr.attrsKey, &opts)
278                         c.Assert(err, check.IsNil)
279                         c.Check(opts.Include, check.DeepEquals, []string{"container_uuid", "owner_uuid"})
280                         if _, ok := params["include"].([]string); ok {
281                                 c.Check(params["include"], check.DeepEquals, []string{"container_uuid", "owner_uuid"})
282                         } else {
283                                 c.Check(params["include"], check.DeepEquals, []interface{}{"container_uuid", "owner_uuid"})
284                         }
285                 }
286         }
287 }