1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
20 "git.curoverse.com/arvados.git/lib/config"
21 "git.curoverse.com/arvados.git/sdk/go/arvados"
22 "git.curoverse.com/arvados.git/sdk/go/arvadostest"
23 "git.curoverse.com/arvados.git/sdk/go/auth"
24 check "gopkg.in/check.v1"
27 var _ = check.Suite(&UnitSuite{})
29 type UnitSuite struct {
30 Config *arvados.Config
33 func (s *UnitSuite) SetUpTest(c *check.C) {
34 ldr := config.NewLoader(nil, nil)
35 cfg, err := ldr.LoadDefaults()
36 c.Assert(err, check.IsNil)
40 func (s *UnitSuite) TestCORSPreflight(c *check.C) {
41 h := handler{Config: DefaultConfig(s.Config)}
42 u := mustParseURL("http://keep-web.example/c=" + arvadostest.FooCollection + "/foo")
47 RequestURI: u.RequestURI(),
49 "Origin": {"https://workbench.example"},
50 "Access-Control-Request-Method": {"POST"},
54 // Check preflight for an allowed request
55 resp := httptest.NewRecorder()
56 h.ServeHTTP(resp, req)
57 c.Check(resp.Code, check.Equals, http.StatusOK)
58 c.Check(resp.Body.String(), check.Equals, "")
59 c.Check(resp.Header().Get("Access-Control-Allow-Origin"), check.Equals, "*")
60 c.Check(resp.Header().Get("Access-Control-Allow-Methods"), check.Equals, "COPY, DELETE, GET, LOCK, MKCOL, MOVE, OPTIONS, POST, PROPFIND, PROPPATCH, PUT, RMCOL, UNLOCK")
61 c.Check(resp.Header().Get("Access-Control-Allow-Headers"), check.Equals, "Authorization, Content-Type, Range, Depth, Destination, If, Lock-Token, Overwrite, Timeout")
63 // Check preflight for a disallowed request
64 resp = httptest.NewRecorder()
65 req.Header.Set("Access-Control-Request-Method", "MAKE-COFFEE")
66 h.ServeHTTP(resp, req)
67 c.Check(resp.Body.String(), check.Equals, "")
68 c.Check(resp.Code, check.Equals, http.StatusMethodNotAllowed)
71 func (s *UnitSuite) TestInvalidUUID(c *check.C) {
72 bogusID := strings.Replace(arvadostest.FooCollectionPDH, "+", "-", 1) + "-"
73 token := arvadostest.ActiveToken
74 for _, trial := range []string{
75 "http://keep-web/c=" + bogusID + "/foo",
76 "http://keep-web/c=" + bogusID + "/t=" + token + "/foo",
77 "http://keep-web/collections/download/" + bogusID + "/" + token + "/foo",
78 "http://keep-web/collections/" + bogusID + "/foo",
79 "http://" + bogusID + ".keep-web/" + bogusID + "/foo",
80 "http://" + bogusID + ".keep-web/t=" + token + "/" + bogusID + "/foo",
83 u := mustParseURL(trial)
88 RequestURI: u.RequestURI(),
90 resp := httptest.NewRecorder()
91 cfg := DefaultConfig(s.Config)
92 cfg.cluster.Users.AnonymousUserToken = arvadostest.AnonymousToken
93 h := handler{Config: cfg}
94 h.ServeHTTP(resp, req)
95 c.Check(resp.Code, check.Equals, http.StatusNotFound)
99 func mustParseURL(s string) *url.URL {
100 r, err := url.Parse(s)
102 panic("parse URL: " + s)
107 func (s *IntegrationSuite) TestVhost404(c *check.C) {
108 for _, testURL := range []string{
109 arvadostest.NonexistentCollection + ".example.com/theperthcountyconspiracy",
110 arvadostest.NonexistentCollection + ".example.com/t=" + arvadostest.ActiveToken + "/theperthcountyconspiracy",
112 resp := httptest.NewRecorder()
113 u := mustParseURL(testURL)
114 req := &http.Request{
117 RequestURI: u.RequestURI(),
119 s.testServer.Handler.ServeHTTP(resp, req)
120 c.Check(resp.Code, check.Equals, http.StatusNotFound)
121 c.Check(resp.Body.String(), check.Equals, "")
125 // An authorizer modifies an HTTP request to make use of the given
126 // token -- by adding it to a header, cookie, query param, or whatever
127 // -- and returns the HTTP status code we should expect from keep-web if
128 // the token is invalid.
129 type authorizer func(*http.Request, string) int
131 func (s *IntegrationSuite) TestVhostViaAuthzHeader(c *check.C) {
132 s.doVhostRequests(c, authzViaAuthzHeader)
134 func authzViaAuthzHeader(r *http.Request, tok string) int {
135 r.Header.Add("Authorization", "OAuth2 "+tok)
136 return http.StatusUnauthorized
139 func (s *IntegrationSuite) TestVhostViaCookieValue(c *check.C) {
140 s.doVhostRequests(c, authzViaCookieValue)
142 func authzViaCookieValue(r *http.Request, tok string) int {
143 r.AddCookie(&http.Cookie{
144 Name: "arvados_api_token",
145 Value: auth.EncodeTokenCookie([]byte(tok)),
147 return http.StatusUnauthorized
150 func (s *IntegrationSuite) TestVhostViaPath(c *check.C) {
151 s.doVhostRequests(c, authzViaPath)
153 func authzViaPath(r *http.Request, tok string) int {
154 r.URL.Path = "/t=" + tok + r.URL.Path
155 return http.StatusNotFound
158 func (s *IntegrationSuite) TestVhostViaQueryString(c *check.C) {
159 s.doVhostRequests(c, authzViaQueryString)
161 func authzViaQueryString(r *http.Request, tok string) int {
162 r.URL.RawQuery = "api_token=" + tok
163 return http.StatusUnauthorized
166 func (s *IntegrationSuite) TestVhostViaPOST(c *check.C) {
167 s.doVhostRequests(c, authzViaPOST)
169 func authzViaPOST(r *http.Request, tok string) int {
171 r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
172 r.Body = ioutil.NopCloser(strings.NewReader(
173 url.Values{"api_token": {tok}}.Encode()))
174 return http.StatusUnauthorized
177 func (s *IntegrationSuite) TestVhostViaXHRPOST(c *check.C) {
178 s.doVhostRequests(c, authzViaPOST)
180 func authzViaXHRPOST(r *http.Request, tok string) int {
182 r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
183 r.Header.Add("Origin", "https://origin.example")
184 r.Body = ioutil.NopCloser(strings.NewReader(
187 "disposition": {"attachment"},
189 return http.StatusUnauthorized
192 // Try some combinations of {url, token} using the given authorization
193 // mechanism, and verify the result is correct.
194 func (s *IntegrationSuite) doVhostRequests(c *check.C, authz authorizer) {
195 for _, hostPath := range []string{
196 arvadostest.FooCollection + ".example.com/foo",
197 arvadostest.FooCollection + "--collections.example.com/foo",
198 arvadostest.FooCollection + "--collections.example.com/_/foo",
199 arvadostest.FooCollectionPDH + ".example.com/foo",
200 strings.Replace(arvadostest.FooCollectionPDH, "+", "-", -1) + "--collections.example.com/foo",
201 arvadostest.FooBarDirCollection + ".example.com/dir1/foo",
203 c.Log("doRequests: ", hostPath)
204 s.doVhostRequestsWithHostPath(c, authz, hostPath)
208 func (s *IntegrationSuite) doVhostRequestsWithHostPath(c *check.C, authz authorizer, hostPath string) {
209 for _, tok := range []string{
210 arvadostest.ActiveToken,
211 arvadostest.ActiveToken[:15],
212 arvadostest.SpectatorToken,
216 u := mustParseURL("http://" + hostPath)
217 req := &http.Request{
221 RequestURI: u.RequestURI(),
222 Header: http.Header{},
224 failCode := authz(req, tok)
225 req, resp := s.doReq(req)
226 code, body := resp.Code, resp.Body.String()
228 // If the initial request had a (non-empty) token
229 // showing in the query string, we should have been
230 // redirected in order to hide it in a cookie.
231 c.Check(req.URL.String(), check.Not(check.Matches), `.*api_token=.+`)
233 if tok == arvadostest.ActiveToken {
234 c.Check(code, check.Equals, http.StatusOK)
235 c.Check(body, check.Equals, "foo")
238 c.Check(code >= 400, check.Equals, true)
239 c.Check(code < 500, check.Equals, true)
240 if tok == arvadostest.SpectatorToken {
241 // Valid token never offers to retry
242 // with different credentials.
243 c.Check(code, check.Equals, http.StatusNotFound)
245 // Invalid token can ask to retry
246 // depending on the authz method.
247 c.Check(code, check.Equals, failCode)
249 c.Check(body, check.Equals, "")
254 func (s *IntegrationSuite) doReq(req *http.Request) (*http.Request, *httptest.ResponseRecorder) {
255 resp := httptest.NewRecorder()
256 s.testServer.Handler.ServeHTTP(resp, req)
257 if resp.Code != http.StatusSeeOther {
260 cookies := (&http.Response{Header: resp.Header()}).Cookies()
261 u, _ := req.URL.Parse(resp.Header().Get("Location"))
266 RequestURI: u.RequestURI(),
267 Header: http.Header{},
269 for _, c := range cookies {
275 func (s *IntegrationSuite) TestVhostRedirectQueryTokenToCookie(c *check.C) {
276 s.testVhostRedirectTokenToCookie(c, "GET",
277 arvadostest.FooCollection+".example.com/foo",
278 "?api_token="+arvadostest.ActiveToken,
286 func (s *IntegrationSuite) TestSingleOriginSecretLink(c *check.C) {
287 s.testVhostRedirectTokenToCookie(c, "GET",
288 "example.com/c="+arvadostest.FooCollection+"/t="+arvadostest.ActiveToken+"/foo",
297 // Bad token in URL is 404 Not Found because it doesn't make sense to
298 // retry the same URL with different authorization.
299 func (s *IntegrationSuite) TestSingleOriginSecretLinkBadToken(c *check.C) {
300 s.testVhostRedirectTokenToCookie(c, "GET",
301 "example.com/c="+arvadostest.FooCollection+"/t=bogus/foo",
310 // Bad token in a cookie (even if it got there via our own
311 // query-string-to-cookie redirect) is, in principle, retryable at the
312 // same URL so it's 401 Unauthorized.
313 func (s *IntegrationSuite) TestVhostRedirectQueryTokenToBogusCookie(c *check.C) {
314 s.testVhostRedirectTokenToCookie(c, "GET",
315 arvadostest.FooCollection+".example.com/foo",
316 "?api_token=thisisabogustoken",
319 http.StatusUnauthorized,
324 func (s *IntegrationSuite) TestVhostRedirectQueryTokenSingleOriginError(c *check.C) {
325 s.testVhostRedirectTokenToCookie(c, "GET",
326 "example.com/c="+arvadostest.FooCollection+"/foo",
327 "?api_token="+arvadostest.ActiveToken,
330 http.StatusBadRequest,
335 // If client requests an attachment by putting ?disposition=attachment
336 // in the query string, and gets redirected, the redirect target
337 // should respond with an attachment.
338 func (s *IntegrationSuite) TestVhostRedirectQueryTokenRequestAttachment(c *check.C) {
339 resp := s.testVhostRedirectTokenToCookie(c, "GET",
340 arvadostest.FooCollection+".example.com/foo",
341 "?disposition=attachment&api_token="+arvadostest.ActiveToken,
347 c.Check(resp.Header().Get("Content-Disposition"), check.Matches, "attachment(;.*)?")
350 func (s *IntegrationSuite) TestVhostRedirectQueryTokenSiteFS(c *check.C) {
351 s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = "download.example.com"
352 resp := s.testVhostRedirectTokenToCookie(c, "GET",
353 "download.example.com/by_id/"+arvadostest.FooCollection+"/foo",
354 "?api_token="+arvadostest.ActiveToken,
360 c.Check(resp.Header().Get("Content-Disposition"), check.Matches, "attachment(;.*)?")
363 func (s *IntegrationSuite) TestPastCollectionVersionFileAccess(c *check.C) {
364 s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = "download.example.com"
365 resp := s.testVhostRedirectTokenToCookie(c, "GET",
366 "download.example.com/c="+arvadostest.WazVersion1Collection+"/waz",
367 "?api_token="+arvadostest.ActiveToken,
373 c.Check(resp.Header().Get("Content-Disposition"), check.Matches, "attachment(;.*)?")
374 resp = s.testVhostRedirectTokenToCookie(c, "GET",
375 "download.example.com/by_id/"+arvadostest.WazVersion1Collection+"/waz",
376 "?api_token="+arvadostest.ActiveToken,
382 c.Check(resp.Header().Get("Content-Disposition"), check.Matches, "attachment(;.*)?")
385 func (s *IntegrationSuite) TestVhostRedirectQueryTokenTrustAllContent(c *check.C) {
386 s.testServer.Config.cluster.Collections.TrustAllContent = true
387 s.testVhostRedirectTokenToCookie(c, "GET",
388 "example.com/c="+arvadostest.FooCollection+"/foo",
389 "?api_token="+arvadostest.ActiveToken,
397 func (s *IntegrationSuite) TestVhostRedirectQueryTokenAttachmentOnlyHost(c *check.C) {
398 s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = "example.com:1234"
400 s.testVhostRedirectTokenToCookie(c, "GET",
401 "example.com/c="+arvadostest.FooCollection+"/foo",
402 "?api_token="+arvadostest.ActiveToken,
405 http.StatusBadRequest,
409 resp := s.testVhostRedirectTokenToCookie(c, "GET",
410 "example.com:1234/c="+arvadostest.FooCollection+"/foo",
411 "?api_token="+arvadostest.ActiveToken,
417 c.Check(resp.Header().Get("Content-Disposition"), check.Equals, "attachment")
420 func (s *IntegrationSuite) TestVhostRedirectPOSTFormTokenToCookie(c *check.C) {
421 s.testVhostRedirectTokenToCookie(c, "POST",
422 arvadostest.FooCollection+".example.com/foo",
424 "application/x-www-form-urlencoded",
425 url.Values{"api_token": {arvadostest.ActiveToken}}.Encode(),
431 func (s *IntegrationSuite) TestVhostRedirectPOSTFormTokenToCookie404(c *check.C) {
432 s.testVhostRedirectTokenToCookie(c, "POST",
433 arvadostest.FooCollection+".example.com/foo",
435 "application/x-www-form-urlencoded",
436 url.Values{"api_token": {arvadostest.SpectatorToken}}.Encode(),
442 func (s *IntegrationSuite) TestAnonymousTokenOK(c *check.C) {
443 s.testServer.Config.cluster.Users.AnonymousUserToken = arvadostest.AnonymousToken
444 s.testVhostRedirectTokenToCookie(c, "GET",
445 "example.com/c="+arvadostest.HelloWorldCollection+"/Hello%20world.txt",
454 func (s *IntegrationSuite) TestAnonymousTokenError(c *check.C) {
455 s.testServer.Config.cluster.Users.AnonymousUserToken = "anonymousTokenConfiguredButInvalid"
456 s.testVhostRedirectTokenToCookie(c, "GET",
457 "example.com/c="+arvadostest.HelloWorldCollection+"/Hello%20world.txt",
466 func (s *IntegrationSuite) TestSpecialCharsInPath(c *check.C) {
467 s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = "download.example.com"
469 client := s.testServer.Config.Client
470 client.AuthToken = arvadostest.ActiveToken
471 fs, err := (&arvados.Collection{}).FileSystem(&client, nil)
472 c.Assert(err, check.IsNil)
473 f, err := fs.OpenFile("https:\\\"odd' path chars", os.O_CREATE, 0777)
474 c.Assert(err, check.IsNil)
476 mtxt, err := fs.MarshalManifest(".")
477 c.Assert(err, check.IsNil)
478 var coll arvados.Collection
479 err = client.RequestAndDecode(&coll, "POST", "arvados/v1/collections", nil, map[string]interface{}{
480 "collection": map[string]string{
481 "manifest_text": mtxt,
484 c.Assert(err, check.IsNil)
486 u, _ := url.Parse("http://download.example.com/c=" + coll.UUID + "/")
487 req := &http.Request{
491 RequestURI: u.RequestURI(),
493 "Authorization": {"Bearer " + client.AuthToken},
496 resp := httptest.NewRecorder()
497 s.testServer.Handler.ServeHTTP(resp, req)
498 c.Check(resp.Code, check.Equals, http.StatusOK)
499 c.Check(resp.Body.String(), check.Matches, `(?ms).*href="./https:%5c%22odd%27%20path%20chars"\S+https:\\"odd' path chars.*`)
502 // XHRs can't follow redirect-with-cookie so they rely on method=POST
503 // and disposition=attachment (telling us it's acceptable to respond
504 // with content instead of a redirect) and an Origin header that gets
505 // added automatically by the browser (telling us it's desirable to do
507 func (s *IntegrationSuite) TestXHRNoRedirect(c *check.C) {
508 u, _ := url.Parse("http://example.com/c=" + arvadostest.FooCollection + "/foo")
509 req := &http.Request{
513 RequestURI: u.RequestURI(),
515 "Origin": {"https://origin.example"},
516 "Content-Type": {"application/x-www-form-urlencoded"},
518 Body: ioutil.NopCloser(strings.NewReader(url.Values{
519 "api_token": {arvadostest.ActiveToken},
520 "disposition": {"attachment"},
523 resp := httptest.NewRecorder()
524 s.testServer.Handler.ServeHTTP(resp, req)
525 c.Check(resp.Code, check.Equals, http.StatusOK)
526 c.Check(resp.Body.String(), check.Equals, "foo")
527 c.Check(resp.Header().Get("Access-Control-Allow-Origin"), check.Equals, "*")
530 func (s *IntegrationSuite) testVhostRedirectTokenToCookie(c *check.C, method, hostPath, queryString, contentType, reqBody string, expectStatus int, expectRespBody string) *httptest.ResponseRecorder {
531 u, _ := url.Parse(`http://` + hostPath + queryString)
532 req := &http.Request{
536 RequestURI: u.RequestURI(),
537 Header: http.Header{"Content-Type": {contentType}},
538 Body: ioutil.NopCloser(strings.NewReader(reqBody)),
541 resp := httptest.NewRecorder()
543 c.Check(resp.Code, check.Equals, expectStatus)
544 c.Check(resp.Body.String(), check.Equals, expectRespBody)
547 s.testServer.Handler.ServeHTTP(resp, req)
548 if resp.Code != http.StatusSeeOther {
551 c.Check(resp.Body.String(), check.Matches, `.*href="http://`+regexp.QuoteMeta(html.EscapeString(hostPath))+`(\?[^"]*)?".*`)
552 cookies := (&http.Response{Header: resp.Header()}).Cookies()
554 u, _ = u.Parse(resp.Header().Get("Location"))
559 RequestURI: u.RequestURI(),
560 Header: http.Header{},
562 for _, c := range cookies {
566 resp = httptest.NewRecorder()
567 s.testServer.Handler.ServeHTTP(resp, req)
568 c.Check(resp.Header().Get("Location"), check.Equals, "")
572 func (s *IntegrationSuite) TestDirectoryListing(c *check.C) {
573 s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = "download.example.com"
574 authHeader := http.Header{
575 "Authorization": {"OAuth2 " + arvadostest.ActiveToken},
577 for _, trial := range []struct {
585 uri: strings.Replace(arvadostest.FooAndBarFilesInDirPDH, "+", "-", -1) + ".example.com/",
587 expect: []string{"dir1/foo", "dir1/bar"},
591 uri: strings.Replace(arvadostest.FooAndBarFilesInDirPDH, "+", "-", -1) + ".example.com/dir1/",
593 expect: []string{"foo", "bar"},
596 // This test case fails
598 uri: "download.example.com/collections/" + arvadostest.FooAndBarFilesInDirUUID + "/",
600 expect: []string{"dir1/foo", "dir1/bar"},
604 uri: "download.example.com/users/active/foo_file_in_dir/",
606 expect: []string{"dir1/"},
610 uri: "download.example.com/users/active/foo_file_in_dir/dir1/",
612 expect: []string{"bar"},
616 uri: "download.example.com/",
618 expect: []string{"users/"},
622 uri: "download.example.com/users",
625 expect: []string{"active/"},
629 uri: "download.example.com/users/",
631 expect: []string{"active/"},
635 uri: "download.example.com/users/active",
637 redirect: "/users/active/",
638 expect: []string{"foo_file_in_dir/"},
642 uri: "download.example.com/users/active/",
644 expect: []string{"foo_file_in_dir/"},
648 uri: "collections.example.com/collections/download/" + arvadostest.FooAndBarFilesInDirUUID + "/" + arvadostest.ActiveToken + "/",
650 expect: []string{"dir1/foo", "dir1/bar"},
654 uri: "collections.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID + "/t=" + arvadostest.ActiveToken + "/",
656 expect: []string{"dir1/foo", "dir1/bar"},
660 uri: "collections.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID + "/t=" + arvadostest.ActiveToken,
662 expect: []string{"dir1/foo", "dir1/bar"},
666 uri: "download.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID,
668 expect: []string{"dir1/foo", "dir1/bar"},
672 uri: "download.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID + "/dir1",
674 redirect: "/c=" + arvadostest.FooAndBarFilesInDirUUID + "/dir1/",
675 expect: []string{"foo", "bar"},
679 uri: "download.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID + "/_/dir1/",
681 expect: []string{"foo", "bar"},
685 uri: arvadostest.FooAndBarFilesInDirUUID + ".example.com/dir1?api_token=" + arvadostest.ActiveToken,
688 expect: []string{"foo", "bar"},
692 uri: "collections.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID + "/theperthcountyconspiracydoesnotexist/",
697 uri: "download.example.com/c=" + arvadostest.WazVersion1Collection,
699 expect: []string{"waz"},
703 uri: "download.example.com/by_id/" + arvadostest.WazVersion1Collection,
705 expect: []string{"waz"},
709 comment := check.Commentf("HTML: %q => %q", trial.uri, trial.expect)
710 resp := httptest.NewRecorder()
711 u := mustParseURL("//" + trial.uri)
712 req := &http.Request{
716 RequestURI: u.RequestURI(),
717 Header: copyHeader(trial.header),
719 s.testServer.Handler.ServeHTTP(resp, req)
720 var cookies []*http.Cookie
721 for resp.Code == http.StatusSeeOther {
722 u, _ := req.URL.Parse(resp.Header().Get("Location"))
727 RequestURI: u.RequestURI(),
728 Header: copyHeader(trial.header),
730 cookies = append(cookies, (&http.Response{Header: resp.Header()}).Cookies()...)
731 for _, c := range cookies {
734 resp = httptest.NewRecorder()
735 s.testServer.Handler.ServeHTTP(resp, req)
737 if trial.redirect != "" {
738 c.Check(req.URL.Path, check.Equals, trial.redirect, comment)
740 if trial.expect == nil {
741 c.Check(resp.Code, check.Equals, http.StatusNotFound, comment)
743 c.Check(resp.Code, check.Equals, http.StatusOK, comment)
744 for _, e := range trial.expect {
745 c.Check(resp.Body.String(), check.Matches, `(?ms).*href="./`+e+`".*`, comment)
747 c.Check(resp.Body.String(), check.Matches, `(?ms).*--cut-dirs=`+fmt.Sprintf("%d", trial.cutDirs)+` .*`, comment)
750 comment = check.Commentf("WebDAV: %q => %q", trial.uri, trial.expect)
755 RequestURI: u.RequestURI(),
756 Header: copyHeader(trial.header),
757 Body: ioutil.NopCloser(&bytes.Buffer{}),
759 resp = httptest.NewRecorder()
760 s.testServer.Handler.ServeHTTP(resp, req)
761 if trial.expect == nil {
762 c.Check(resp.Code, check.Equals, http.StatusNotFound, comment)
764 c.Check(resp.Code, check.Equals, http.StatusOK, comment)
771 RequestURI: u.RequestURI(),
772 Header: copyHeader(trial.header),
773 Body: ioutil.NopCloser(&bytes.Buffer{}),
775 resp = httptest.NewRecorder()
776 s.testServer.Handler.ServeHTTP(resp, req)
777 if trial.expect == nil {
778 c.Check(resp.Code, check.Equals, http.StatusNotFound, comment)
780 c.Check(resp.Code, check.Equals, http.StatusMultiStatus, comment)
781 for _, e := range trial.expect {
782 c.Check(resp.Body.String(), check.Matches, `(?ms).*<D:href>`+filepath.Join(u.Path, e)+`</D:href>.*`, comment)
788 func (s *IntegrationSuite) TestDeleteLastFile(c *check.C) {
789 arv := arvados.NewClientFromEnv()
790 var newCollection arvados.Collection
791 err := arv.RequestAndDecode(&newCollection, "POST", "arvados/v1/collections", nil, map[string]interface{}{
792 "collection": map[string]string{
793 "owner_uuid": arvadostest.ActiveUserUUID,
794 "manifest_text": ". acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:foo.txt 0:3:bar.txt\n",
795 "name": "keep-web test collection",
797 "ensure_unique_name": true,
799 c.Assert(err, check.IsNil)
800 defer arv.RequestAndDecode(&newCollection, "DELETE", "arvados/v1/collections/"+newCollection.UUID, nil, nil)
802 var updated arvados.Collection
803 for _, fnm := range []string{"foo.txt", "bar.txt"} {
804 s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = "example.com"
805 u, _ := url.Parse("http://example.com/c=" + newCollection.UUID + "/" + fnm)
806 req := &http.Request{
810 RequestURI: u.RequestURI(),
812 "Authorization": {"Bearer " + arvadostest.ActiveToken},
815 resp := httptest.NewRecorder()
816 s.testServer.Handler.ServeHTTP(resp, req)
817 c.Check(resp.Code, check.Equals, http.StatusNoContent)
819 updated = arvados.Collection{}
820 err = arv.RequestAndDecode(&updated, "GET", "arvados/v1/collections/"+newCollection.UUID, nil, nil)
821 c.Check(err, check.IsNil)
822 c.Check(updated.ManifestText, check.Not(check.Matches), `(?ms).*\Q`+fnm+`\E.*`)
823 c.Logf("updated manifest_text %q", updated.ManifestText)
825 c.Check(updated.ManifestText, check.Equals, "")
828 func (s *IntegrationSuite) TestHealthCheckPing(c *check.C) {
829 s.testServer.Config.cluster.ManagementToken = arvadostest.ManagementToken
830 authHeader := http.Header{
831 "Authorization": {"Bearer " + arvadostest.ManagementToken},
834 resp := httptest.NewRecorder()
835 u := mustParseURL("http://download.example.com/_health/ping")
836 req := &http.Request{
840 RequestURI: u.RequestURI(),
843 s.testServer.Handler.ServeHTTP(resp, req)
845 c.Check(resp.Code, check.Equals, http.StatusOK)
846 c.Check(resp.Body.String(), check.Matches, `{"health":"OK"}\n`)
849 func copyHeader(h http.Header) http.Header {
851 for k, v := range h {
852 hc[k] = append([]string(nil), v...)