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) TestDirectoryListingWithAnonymousToken(c *check.C) {
573 s.testServer.Config.cluster.Users.AnonymousUserToken = arvadostest.AnonymousToken
574 s.testDirectoryListing(c)
577 func (s *IntegrationSuite) TestDirectoryListingWithNoAnonymousToken(c *check.C) {
578 s.testServer.Config.cluster.Users.AnonymousUserToken = ""
579 s.testDirectoryListing(c)
582 func (s *IntegrationSuite) testDirectoryListing(c *check.C) {
583 s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = "download.example.com"
584 authHeader := http.Header{
585 "Authorization": {"OAuth2 " + arvadostest.ActiveToken},
587 for _, trial := range []struct {
595 uri: strings.Replace(arvadostest.FooAndBarFilesInDirPDH, "+", "-", -1) + ".example.com/",
597 expect: []string{"dir1/foo", "dir1/bar"},
601 uri: strings.Replace(arvadostest.FooAndBarFilesInDirPDH, "+", "-", -1) + ".example.com/dir1/",
603 expect: []string{"foo", "bar"},
607 // URLs of this form ignore authHeader, and
608 // FooAndBarFilesInDirUUID isn't public, so
610 uri: "download.example.com/collections/" + arvadostest.FooAndBarFilesInDirUUID + "/",
615 uri: "download.example.com/users/active/foo_file_in_dir/",
617 expect: []string{"dir1/"},
621 uri: "download.example.com/users/active/foo_file_in_dir/dir1/",
623 expect: []string{"bar"},
627 uri: "download.example.com/",
629 expect: []string{"users/"},
633 uri: "download.example.com/users",
636 expect: []string{"active/"},
640 uri: "download.example.com/users/",
642 expect: []string{"active/"},
646 uri: "download.example.com/users/active",
648 redirect: "/users/active/",
649 expect: []string{"foo_file_in_dir/"},
653 uri: "download.example.com/users/active/",
655 expect: []string{"foo_file_in_dir/"},
659 uri: "collections.example.com/collections/download/" + arvadostest.FooAndBarFilesInDirUUID + "/" + arvadostest.ActiveToken + "/",
661 expect: []string{"dir1/foo", "dir1/bar"},
665 uri: "collections.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID + "/t=" + arvadostest.ActiveToken + "/",
667 expect: []string{"dir1/foo", "dir1/bar"},
671 uri: "collections.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID + "/t=" + arvadostest.ActiveToken,
673 expect: []string{"dir1/foo", "dir1/bar"},
677 uri: "download.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID,
679 expect: []string{"dir1/foo", "dir1/bar"},
683 uri: "download.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID + "/dir1",
685 redirect: "/c=" + arvadostest.FooAndBarFilesInDirUUID + "/dir1/",
686 expect: []string{"foo", "bar"},
690 uri: "download.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID + "/_/dir1/",
692 expect: []string{"foo", "bar"},
696 uri: arvadostest.FooAndBarFilesInDirUUID + ".example.com/dir1?api_token=" + arvadostest.ActiveToken,
699 expect: []string{"foo", "bar"},
703 uri: "collections.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID + "/theperthcountyconspiracydoesnotexist/",
708 uri: "download.example.com/c=" + arvadostest.WazVersion1Collection,
710 expect: []string{"waz"},
714 uri: "download.example.com/by_id/" + arvadostest.WazVersion1Collection,
716 expect: []string{"waz"},
720 comment := check.Commentf("HTML: %q => %q", trial.uri, trial.expect)
721 resp := httptest.NewRecorder()
722 u := mustParseURL("//" + trial.uri)
723 req := &http.Request{
727 RequestURI: u.RequestURI(),
728 Header: copyHeader(trial.header),
730 s.testServer.Handler.ServeHTTP(resp, req)
731 var cookies []*http.Cookie
732 for resp.Code == http.StatusSeeOther {
733 u, _ := req.URL.Parse(resp.Header().Get("Location"))
738 RequestURI: u.RequestURI(),
739 Header: copyHeader(trial.header),
741 cookies = append(cookies, (&http.Response{Header: resp.Header()}).Cookies()...)
742 for _, c := range cookies {
745 resp = httptest.NewRecorder()
746 s.testServer.Handler.ServeHTTP(resp, req)
748 if trial.redirect != "" {
749 c.Check(req.URL.Path, check.Equals, trial.redirect, comment)
751 if trial.expect == nil {
752 c.Check(resp.Code, check.Equals, http.StatusNotFound, comment)
754 c.Check(resp.Code, check.Equals, http.StatusOK, comment)
755 for _, e := range trial.expect {
756 c.Check(resp.Body.String(), check.Matches, `(?ms).*href="./`+e+`".*`, comment)
758 c.Check(resp.Body.String(), check.Matches, `(?ms).*--cut-dirs=`+fmt.Sprintf("%d", trial.cutDirs)+` .*`, comment)
761 comment = check.Commentf("WebDAV: %q => %q", trial.uri, trial.expect)
766 RequestURI: u.RequestURI(),
767 Header: copyHeader(trial.header),
768 Body: ioutil.NopCloser(&bytes.Buffer{}),
770 resp = httptest.NewRecorder()
771 s.testServer.Handler.ServeHTTP(resp, req)
772 if trial.expect == nil {
773 c.Check(resp.Code, check.Equals, http.StatusNotFound, comment)
775 c.Check(resp.Code, check.Equals, http.StatusOK, comment)
782 RequestURI: u.RequestURI(),
783 Header: copyHeader(trial.header),
784 Body: ioutil.NopCloser(&bytes.Buffer{}),
786 resp = httptest.NewRecorder()
787 s.testServer.Handler.ServeHTTP(resp, req)
788 if trial.expect == nil {
789 c.Check(resp.Code, check.Equals, http.StatusNotFound, comment)
791 c.Check(resp.Code, check.Equals, http.StatusMultiStatus, comment)
792 for _, e := range trial.expect {
793 c.Check(resp.Body.String(), check.Matches, `(?ms).*<D:href>`+filepath.Join(u.Path, e)+`</D:href>.*`, comment)
799 func (s *IntegrationSuite) TestDeleteLastFile(c *check.C) {
800 arv := arvados.NewClientFromEnv()
801 var newCollection arvados.Collection
802 err := arv.RequestAndDecode(&newCollection, "POST", "arvados/v1/collections", nil, map[string]interface{}{
803 "collection": map[string]string{
804 "owner_uuid": arvadostest.ActiveUserUUID,
805 "manifest_text": ". acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:foo.txt 0:3:bar.txt\n",
806 "name": "keep-web test collection",
808 "ensure_unique_name": true,
810 c.Assert(err, check.IsNil)
811 defer arv.RequestAndDecode(&newCollection, "DELETE", "arvados/v1/collections/"+newCollection.UUID, nil, nil)
813 var updated arvados.Collection
814 for _, fnm := range []string{"foo.txt", "bar.txt"} {
815 s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = "example.com"
816 u, _ := url.Parse("http://example.com/c=" + newCollection.UUID + "/" + fnm)
817 req := &http.Request{
821 RequestURI: u.RequestURI(),
823 "Authorization": {"Bearer " + arvadostest.ActiveToken},
826 resp := httptest.NewRecorder()
827 s.testServer.Handler.ServeHTTP(resp, req)
828 c.Check(resp.Code, check.Equals, http.StatusNoContent)
830 updated = arvados.Collection{}
831 err = arv.RequestAndDecode(&updated, "GET", "arvados/v1/collections/"+newCollection.UUID, nil, nil)
832 c.Check(err, check.IsNil)
833 c.Check(updated.ManifestText, check.Not(check.Matches), `(?ms).*\Q`+fnm+`\E.*`)
834 c.Logf("updated manifest_text %q", updated.ManifestText)
836 c.Check(updated.ManifestText, check.Equals, "")
839 func (s *IntegrationSuite) TestHealthCheckPing(c *check.C) {
840 s.testServer.Config.cluster.ManagementToken = arvadostest.ManagementToken
841 authHeader := http.Header{
842 "Authorization": {"Bearer " + arvadostest.ManagementToken},
845 resp := httptest.NewRecorder()
846 u := mustParseURL("http://download.example.com/_health/ping")
847 req := &http.Request{
851 RequestURI: u.RequestURI(),
854 s.testServer.Handler.ServeHTTP(resp, req)
856 c.Check(resp.Code, check.Equals, http.StatusOK)
857 c.Check(resp.Body.String(), check.Matches, `{"health":"OK"}\n`)
860 func copyHeader(h http.Header) http.Header {
862 for k, v := range h {
863 hc[k] = append([]string(nil), v...)