1 import os.path, re, urllib
3 from twisted.application import service
4 from twisted.trial import unittest
5 from twisted.internet import defer, reactor
6 from twisted.web import client, error, http
7 from twisted.python import failure, log
8 from allmydata import interfaces, provisioning, uri, webish
9 from allmydata.immutable import upload, download
10 from allmydata.web import status, common
11 from allmydata.util import fileutil, base32
12 from allmydata.test.common import FakeDirectoryNode, FakeCHKFileNode, \
13 FakeMutableFileNode, create_chk_filenode
14 from allmydata.interfaces import IURI, INewDirectoryURI, \
15 IReadonlyNewDirectoryURI, IFileURI, IMutableFileURI, IMutableFileNode
16 from allmydata.mutable import servermap, publish, retrieve
17 import common_util as testutil
19 # create a fake uploader/downloader, and a couple of fake dirnodes, then
20 # create a webserver that works against them
22 class FakeIntroducerClient:
23 def get_all_connectors(self):
25 def get_all_connections_for(self, service_name):
27 def get_all_peerids(self):
30 class FakeClient(service.MultiService):
31 nodeid = "fake_nodeid"
32 nickname = "fake_nickname"
33 basedir = "fake_basedir"
34 def get_versions(self):
35 return {'allmydata': "fake",
40 introducer_furl = "None"
41 introducer_client = FakeIntroducerClient()
42 _all_upload_status = [upload.UploadStatus()]
43 _all_download_status = [download.DownloadStatus()]
44 _all_mapupdate_statuses = [servermap.UpdateStatus()]
45 _all_publish_statuses = [publish.PublishStatus()]
46 _all_retrieve_statuses = [retrieve.RetrieveStatus()]
47 convergence = "some random string"
49 def connected_to_introducer(self):
52 def get_nickname_for_peerid(self, peerid):
55 def create_node_from_uri(self, auri):
56 u = uri.from_string(auri)
57 if (INewDirectoryURI.providedBy(u)
58 or IReadonlyNewDirectoryURI.providedBy(u)):
59 return FakeDirectoryNode(self).init_from_uri(u)
60 if IFileURI.providedBy(u):
61 return FakeCHKFileNode(u, self)
62 assert IMutableFileURI.providedBy(u), u
63 return FakeMutableFileNode(self).init_from_uri(u)
65 def create_empty_dirnode(self):
66 n = FakeDirectoryNode(self)
68 d.addCallback(lambda res: n)
71 MUTABLE_SIZELIMIT = FakeMutableFileNode.MUTABLE_SIZELIMIT
72 def create_mutable_file(self, contents=""):
73 n = FakeMutableFileNode(self)
74 return n.create(contents)
76 def upload(self, uploadable):
77 d = uploadable.get_size()
78 d.addCallback(lambda size: uploadable.read(size))
81 n = create_chk_filenode(self, data)
82 results = upload.UploadResults()
83 results.uri = n.get_uri()
85 d.addCallback(_got_data)
88 def list_all_upload_statuses(self):
89 return self._all_upload_status
90 def list_all_download_statuses(self):
91 return self._all_download_status
92 def list_all_mapupdate_statuses(self):
93 return self._all_mapupdate_statuses
94 def list_all_publish_statuses(self):
95 return self._all_publish_statuses
96 def list_all_retrieve_statuses(self):
97 return self._all_retrieve_statuses
98 def list_all_helper_statuses(self):
101 class MyGetter(client.HTTPPageGetter):
102 handleStatus_206 = lambda self: self.handleStatus_200()
104 class HTTPClientHEADFactory(client.HTTPClientFactory):
107 def noPage(self, reason):
108 # Twisted-2.5.0 and earlier had a bug, in which they would raise an
109 # exception when the response to a HEAD request had no body (when in
110 # fact they are defined to never have a body). This was fixed in
111 # Twisted-8.0 . To work around this, we catch the
112 # PartialDownloadError and make it disappear.
113 if (reason.check(client.PartialDownloadError)
114 and self.method.upper() == "HEAD"):
117 return client.HTTPClientFactory.noPage(self, reason)
119 class HTTPClientGETFactory(client.HTTPClientFactory):
122 class WebMixin(object):
124 self.s = FakeClient()
125 self.s.startService()
126 self.staticdir = self.mktemp()
127 self.ws = s = webish.WebishServer("0", staticdir=self.staticdir)
128 s.setServiceParent(self.s)
129 self.webish_port = port = s.listener._port.getHost().port
130 self.webish_url = "http://localhost:%d" % port
132 l = [ self.s.create_empty_dirnode() for x in range(6) ]
133 d = defer.DeferredList(l)
135 self.public_root = res[0][1]
136 assert interfaces.IDirectoryNode.providedBy(self.public_root), res
137 self.public_url = "/uri/" + self.public_root.get_uri()
138 self.private_root = res[1][1]
142 self._foo_uri = foo.get_uri()
143 self._foo_readonly_uri = foo.get_readonly_uri()
144 # NOTE: we ignore the deferred on all set_uri() calls, because we
145 # know the fake nodes do these synchronously
146 self.public_root.set_uri(u"foo", foo.get_uri())
148 self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
149 foo.set_uri(u"bar.txt", self._bar_txt_uri)
151 foo.set_uri(u"empty", res[3][1].get_uri())
152 sub_uri = res[4][1].get_uri()
153 self._sub_uri = sub_uri
154 foo.set_uri(u"sub", sub_uri)
155 sub = self.s.create_node_from_uri(sub_uri)
157 _ign, n, blocking_uri = self.makefile(1)
158 foo.set_uri(u"blockingfile", blocking_uri)
160 unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
161 # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
162 # still think of it as an umlaut
163 foo.set_uri(unicode_filename, self._bar_txt_uri)
165 _ign, n, baz_file = self.makefile(2)
166 sub.set_uri(u"baz.txt", baz_file)
168 _ign, n, self._bad_file_uri = self.makefile(3)
169 # this uri should not be downloadable
170 del FakeCHKFileNode.all_contents[self._bad_file_uri]
173 self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri())
174 rodir.set_uri(u"nor", baz_file)
179 # public/foo/blockingfile
182 # public/foo/sub/baz.txt
184 # public/reedownlee/nor
185 self.NEWFILE_CONTENTS = "newfile contents\n"
187 return foo.get_metadata_for(u"bar.txt")
189 def _got_metadata(metadata):
190 self._bar_txt_metadata = metadata
191 d.addCallback(_got_metadata)
194 def makefile(self, number):
195 contents = "contents of file %s\n" % number
196 n = create_chk_filenode(self.s, contents)
197 return contents, n, n.get_uri()
200 return self.s.stopService()
202 def failUnlessIsBarDotTxt(self, res):
203 self.failUnlessEqual(res, self.BAR_CONTENTS, res)
205 def failUnlessIsBarJSON(self, res):
206 data = simplejson.loads(res)
207 self.failUnless(isinstance(data, list))
208 self.failUnlessEqual(data[0], u"filenode")
209 self.failUnless(isinstance(data[1], dict))
210 self.failIf(data[1]["mutable"])
211 self.failIf("rw_uri" in data[1]) # immutable
212 self.failUnlessEqual(data[1]["ro_uri"], self._bar_txt_uri)
213 self.failUnlessEqual(data[1]["size"], len(self.BAR_CONTENTS))
215 def failUnlessIsFooJSON(self, res):
216 data = simplejson.loads(res)
217 self.failUnless(isinstance(data, list))
218 self.failUnlessEqual(data[0], "dirnode", res)
219 self.failUnless(isinstance(data[1], dict))
220 self.failUnless(data[1]["mutable"])
221 self.failUnless("rw_uri" in data[1]) # mutable
222 self.failUnlessEqual(data[1]["rw_uri"], self._foo_uri)
223 self.failUnlessEqual(data[1]["ro_uri"], self._foo_readonly_uri)
225 kidnames = sorted([unicode(n) for n in data[1]["children"]])
226 self.failUnlessEqual(kidnames,
227 [u"bar.txt", u"blockingfile", u"empty",
228 u"n\u00fc.txt", u"sub"])
229 kids = dict( [(unicode(name),value)
231 in data[1]["children"].iteritems()] )
232 self.failUnlessEqual(kids[u"sub"][0], "dirnode")
233 self.failUnless("metadata" in kids[u"sub"][1])
234 self.failUnless("ctime" in kids[u"sub"][1]["metadata"])
235 self.failUnless("mtime" in kids[u"sub"][1]["metadata"])
236 self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
237 self.failUnlessEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
238 self.failUnlessEqual(kids[u"bar.txt"][1]["ro_uri"], self._bar_txt_uri)
239 self.failUnlessEqual(kids[u"bar.txt"][1]["metadata"]["ctime"],
240 self._bar_txt_metadata["ctime"])
241 self.failUnlessEqual(kids[u"n\u00fc.txt"][1]["ro_uri"],
244 def GET(self, urlpath, followRedirect=False, return_response=False,
246 # if return_response=True, this fires with (data, statuscode,
247 # respheaders) instead of just data.
248 assert not isinstance(urlpath, unicode)
249 url = self.webish_url + urlpath
250 factory = HTTPClientGETFactory(url, method="GET",
251 followRedirect=followRedirect, **kwargs)
252 reactor.connectTCP("localhost", self.webish_port, factory)
255 return (data, factory.status, factory.response_headers)
257 d.addCallback(_got_data)
258 return factory.deferred
260 def HEAD(self, urlpath, return_response=False, **kwargs):
261 # this requires some surgery, because twisted.web.client doesn't want
262 # to give us back the response headers.
263 factory = HTTPClientHEADFactory(urlpath, method="HEAD", **kwargs)
264 reactor.connectTCP("localhost", self.webish_port, factory)
267 return (data, factory.status, factory.response_headers)
269 d.addCallback(_got_data)
270 return factory.deferred
272 def PUT(self, urlpath, data, **kwargs):
273 url = self.webish_url + urlpath
274 return client.getPage(url, method="PUT", postdata=data, **kwargs)
276 def DELETE(self, urlpath):
277 url = self.webish_url + urlpath
278 return client.getPage(url, method="DELETE")
280 def POST(self, urlpath, followRedirect=False, **fields):
281 url = self.webish_url + urlpath
282 sepbase = "boogabooga"
286 form.append('Content-Disposition: form-data; name="_charset"')
290 for name, value in fields.iteritems():
291 if isinstance(value, tuple):
292 filename, value = value
293 form.append('Content-Disposition: form-data; name="%s"; '
294 'filename="%s"' % (name, filename.encode("utf-8")))
296 form.append('Content-Disposition: form-data; name="%s"' % name)
298 if isinstance(value, unicode):
299 value = value.encode("utf-8")
302 assert isinstance(value, str)
306 body = "\r\n".join(form) + "\r\n"
307 headers = {"content-type": "multipart/form-data; boundary=%s" % sepbase,
309 return client.getPage(url, method="POST", postdata=body,
310 headers=headers, followRedirect=followRedirect)
312 def shouldFail(self, res, expected_failure, which,
313 substring=None, response_substring=None):
314 if isinstance(res, failure.Failure):
315 res.trap(expected_failure)
317 self.failUnless(substring in str(res),
318 "substring '%s' not in '%s'"
319 % (substring, str(res)))
320 if response_substring:
321 self.failUnless(response_substring in res.value.response,
322 "response substring '%s' not in '%s'"
323 % (response_substring, res.value.response))
325 self.fail("%s was supposed to raise %s, not get '%s'" %
326 (which, expected_failure, res))
328 def shouldFail2(self, expected_failure, which, substring,
330 callable, *args, **kwargs):
331 assert substring is None or isinstance(substring, str)
332 assert response_substring is None or isinstance(response_substring, str)
333 d = defer.maybeDeferred(callable, *args, **kwargs)
335 if isinstance(res, failure.Failure):
336 res.trap(expected_failure)
338 self.failUnless(substring in str(res),
339 "%s: substring '%s' not in '%s'"
340 % (which, substring, str(res)))
341 if response_substring:
342 self.failUnless(response_substring in res.value.response,
343 "%s: response substring '%s' not in '%s'"
345 response_substring, res.value.response))
347 self.fail("%s was supposed to raise %s, not get '%s'" %
348 (which, expected_failure, res))
352 def should404(self, res, which):
353 if isinstance(res, failure.Failure):
354 res.trap(error.Error)
355 self.failUnlessEqual(res.value.status, "404")
357 self.fail("%s was supposed to Error(404), not get '%s'" %
360 def shouldHTTPError(self, res, which, code=None, substring=None,
361 response_substring=None):
362 if isinstance(res, failure.Failure):
363 res.trap(error.Error)
365 self.failUnlessEqual(res.value.status, str(code))
367 self.failUnless(substring in str(res),
368 "substring '%s' not in '%s'"
369 % (substring, str(res)))
370 if response_substring:
371 self.failUnless(response_substring in res.value.response,
372 "response substring '%s' not in '%s'"
373 % (response_substring, res.value.response))
375 self.fail("%s was supposed to Error(%s), not get '%s'" %
378 def shouldHTTPError2(self, which,
379 code=None, substring=None, response_substring=None,
380 callable=None, *args, **kwargs):
381 assert substring is None or isinstance(substring, str)
383 d = defer.maybeDeferred(callable, *args, **kwargs)
384 d.addBoth(self.shouldHTTPError, which,
385 code, substring, response_substring)
389 class Web(WebMixin, testutil.StallMixin, unittest.TestCase):
390 def test_create(self):
393 def test_welcome(self):
396 self.failUnless('Welcome To AllMyData' in res)
397 self.failUnless('Tahoe' in res)
399 self.s.basedir = 'web/test_welcome'
400 fileutil.make_dirs("web/test_welcome")
401 fileutil.make_dirs("web/test_welcome/private")
403 d.addCallback(_check)
406 def test_provisioning_math(self):
407 self.failUnlessEqual(provisioning.binomial(10, 0), 1)
408 self.failUnlessEqual(provisioning.binomial(10, 1), 10)
409 self.failUnlessEqual(provisioning.binomial(10, 2), 45)
410 self.failUnlessEqual(provisioning.binomial(10, 9), 10)
411 self.failUnlessEqual(provisioning.binomial(10, 10), 1)
413 def test_provisioning(self):
414 d = self.GET("/provisioning/")
416 self.failUnless('Tahoe Provisioning Tool' in res)
417 fields = {'filled': True,
418 "num_users": int(50e3),
419 "files_per_user": 1000,
420 "space_per_user": int(1e9),
421 "sharing_ratio": 1.0,
422 "encoding_parameters": "3-of-10-5",
424 "ownership_mode": "A",
425 "download_rate": 100,
430 return self.POST("/provisioning/", **fields)
432 d.addCallback(_check)
434 self.failUnless('Tahoe Provisioning Tool' in res)
435 self.failUnless("Share space consumed: 167.01TB" in res)
437 fields = {'filled': True,
438 "num_users": int(50e6),
439 "files_per_user": 1000,
440 "space_per_user": int(5e9),
441 "sharing_ratio": 1.0,
442 "encoding_parameters": "25-of-100-50",
443 "num_servers": 30000,
444 "ownership_mode": "E",
445 "drive_failure_model": "U",
447 "download_rate": 1000,
452 return self.POST("/provisioning/", **fields)
453 d.addCallback(_check2)
455 self.failUnless("Share space consumed: huge!" in res)
456 fields = {'filled': True}
457 return self.POST("/provisioning/", **fields)
458 d.addCallback(_check3)
460 self.failUnless("Share space consumed:" in res)
461 d.addCallback(_check4)
464 def test_status(self):
465 dl_num = self.s.list_all_download_statuses()[0].get_counter()
466 ul_num = self.s.list_all_upload_statuses()[0].get_counter()
467 mu_num = self.s.list_all_mapupdate_statuses()[0].get_counter()
468 pub_num = self.s.list_all_publish_statuses()[0].get_counter()
469 ret_num = self.s.list_all_retrieve_statuses()[0].get_counter()
470 d = self.GET("/status", followRedirect=True)
472 self.failUnless('Upload and Download Status' in res, res)
473 self.failUnless('"down-%d"' % dl_num in res, res)
474 self.failUnless('"up-%d"' % ul_num in res, res)
475 self.failUnless('"mapupdate-%d"' % mu_num in res, res)
476 self.failUnless('"publish-%d"' % pub_num in res, res)
477 self.failUnless('"retrieve-%d"' % ret_num in res, res)
478 d.addCallback(_check)
479 d.addCallback(lambda res: self.GET("/status/?t=json"))
480 def _check_json(res):
481 data = simplejson.loads(res)
482 self.failUnless(isinstance(data, dict))
483 active = data["active"]
484 # TODO: test more. We need a way to fake an active operation
486 d.addCallback(_check_json)
488 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
490 self.failUnless("File Download Status" in res, res)
491 d.addCallback(_check_dl)
492 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
494 self.failUnless("File Upload Status" in res, res)
495 d.addCallback(_check_ul)
496 d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num))
497 def _check_mapupdate(res):
498 self.failUnless("Mutable File Servermap Update Status" in res, res)
499 d.addCallback(_check_mapupdate)
500 d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num))
501 def _check_publish(res):
502 self.failUnless("Mutable File Publish Status" in res, res)
503 d.addCallback(_check_publish)
504 d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num))
505 def _check_retrieve(res):
506 self.failUnless("Mutable File Retrieve Status" in res, res)
507 d.addCallback(_check_retrieve)
511 def test_status_numbers(self):
512 drrm = status.DownloadResultsRendererMixin()
513 self.failUnlessEqual(drrm.render_time(None, None), "")
514 self.failUnlessEqual(drrm.render_time(None, 2.5), "2.50s")
515 self.failUnlessEqual(drrm.render_time(None, 0.25), "250ms")
516 self.failUnlessEqual(drrm.render_time(None, 0.0021), "2.1ms")
517 self.failUnlessEqual(drrm.render_time(None, 0.000123), "123us")
518 self.failUnlessEqual(drrm.render_rate(None, None), "")
519 self.failUnlessEqual(drrm.render_rate(None, 2500000), "2.50MBps")
520 self.failUnlessEqual(drrm.render_rate(None, 30100), "30.1kBps")
521 self.failUnlessEqual(drrm.render_rate(None, 123), "123Bps")
523 urrm = status.UploadResultsRendererMixin()
524 self.failUnlessEqual(urrm.render_time(None, None), "")
525 self.failUnlessEqual(urrm.render_time(None, 2.5), "2.50s")
526 self.failUnlessEqual(urrm.render_time(None, 0.25), "250ms")
527 self.failUnlessEqual(urrm.render_time(None, 0.0021), "2.1ms")
528 self.failUnlessEqual(urrm.render_time(None, 0.000123), "123us")
529 self.failUnlessEqual(urrm.render_rate(None, None), "")
530 self.failUnlessEqual(urrm.render_rate(None, 2500000), "2.50MBps")
531 self.failUnlessEqual(urrm.render_rate(None, 30100), "30.1kBps")
532 self.failUnlessEqual(urrm.render_rate(None, 123), "123Bps")
534 def test_GET_FILEURL(self):
535 d = self.GET(self.public_url + "/foo/bar.txt")
536 d.addCallback(self.failUnlessIsBarDotTxt)
539 def test_GET_FILEURL_range(self):
540 headers = {"range": "bytes=1-10"}
541 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
542 return_response=True)
543 def _got((res, status, headers)):
544 self.failUnlessEqual(int(status), 206)
545 self.failUnless(headers.has_key("content-range"))
546 self.failUnlessEqual(headers["content-range"][0],
547 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
548 self.failUnlessEqual(res, self.BAR_CONTENTS[1:11])
552 def test_HEAD_FILEURL_range(self):
553 headers = {"range": "bytes=1-10"}
554 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
555 return_response=True)
556 def _got((res, status, headers)):
557 self.failUnlessEqual(res, "")
558 self.failUnlessEqual(int(status), 206)
559 self.failUnless(headers.has_key("content-range"))
560 self.failUnlessEqual(headers["content-range"][0],
561 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
565 def test_GET_FILEURL_range_bad(self):
566 headers = {"range": "BOGUS=fizbop-quarnak"}
567 d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_bad",
569 "Syntactically invalid http range header",
570 self.GET, self.public_url + "/foo/bar.txt",
574 def test_HEAD_FILEURL(self):
575 d = self.HEAD(self.public_url + "/foo/bar.txt", return_response=True)
576 def _got((res, status, headers)):
577 self.failUnlessEqual(res, "")
578 self.failUnlessEqual(headers["content-length"][0],
579 str(len(self.BAR_CONTENTS)))
580 self.failUnlessEqual(headers["content-type"], ["text/plain"])
584 def test_GET_FILEURL_named(self):
585 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
586 base2 = "/named/%s" % urllib.quote(self._bar_txt_uri)
587 d = self.GET(base + "/@@name=/blah.txt")
588 d.addCallback(self.failUnlessIsBarDotTxt)
589 d.addCallback(lambda res: self.GET(base + "/blah.txt"))
590 d.addCallback(self.failUnlessIsBarDotTxt)
591 d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt"))
592 d.addCallback(self.failUnlessIsBarDotTxt)
593 d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt"))
594 d.addCallback(self.failUnlessIsBarDotTxt)
595 save_url = base + "?save=true&filename=blah.txt"
596 d.addCallback(lambda res: self.GET(save_url))
597 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
598 u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
599 u_fn_e = urllib.quote(u_filename.encode("utf-8"))
600 u_url = base + "?save=true&filename=" + u_fn_e
601 d.addCallback(lambda res: self.GET(u_url))
602 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
605 def test_PUT_FILEURL_named_bad(self):
606 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
607 d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad",
609 "/file can only be used with GET or HEAD",
610 self.PUT, base + "/@@name=/blah.txt", "")
613 def test_GET_DIRURL_named_bad(self):
614 base = "/file/%s" % urllib.quote(self._foo_uri)
615 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
618 self.GET, base + "/@@name=/blah.txt")
621 def test_GET_slash_file_bad(self):
622 d = self.shouldFail2(error.Error, "test_GET_slash_file_bad",
624 "/file must be followed by a file-cap and a name",
628 def test_GET_unhandled_URI_named(self):
629 contents, n, newuri = self.makefile(12)
630 verifier_cap = n.get_verifier().to_string()
631 base = "/file/%s" % urllib.quote(verifier_cap)
632 # client.create_node_from_uri() can't handle verify-caps
633 d = self.shouldFail2(error.Error, "GET_unhandled_URI_named",
635 "is not a valid file- or directory- cap",
639 def test_GET_unhandled_URI(self):
640 contents, n, newuri = self.makefile(12)
641 verifier_cap = n.get_verifier().to_string()
642 base = "/uri/%s" % urllib.quote(verifier_cap)
643 # client.create_node_from_uri() can't handle verify-caps
644 d = self.shouldFail2(error.Error, "test_GET_unhandled_URI",
646 "is not a valid file- or directory- cap",
650 def test_GET_FILE_URI(self):
651 base = "/uri/%s" % urllib.quote(self._bar_txt_uri)
653 d.addCallback(self.failUnlessIsBarDotTxt)
656 def test_GET_FILE_URI_badchild(self):
657 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
658 errmsg = "Files have no children, certainly not named 'boguschild'"
659 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
660 "400 Bad Request", errmsg,
664 def test_PUT_FILE_URI_badchild(self):
665 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
666 errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory"
667 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
668 "400 Bad Request", errmsg,
672 def test_GET_FILEURL_save(self):
673 d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true")
674 # TODO: look at the headers, expect a Content-Disposition: attachment
676 d.addCallback(self.failUnlessIsBarDotTxt)
679 def test_GET_FILEURL_missing(self):
680 d = self.GET(self.public_url + "/foo/missing")
681 d.addBoth(self.should404, "test_GET_FILEURL_missing")
684 def test_PUT_NEWFILEURL(self):
685 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
686 # TODO: we lose the response code, so we can't check this
687 #self.failUnlessEqual(responsecode, 201)
688 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
689 d.addCallback(lambda res:
690 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
691 self.NEWFILE_CONTENTS))
694 def test_PUT_NEWFILEURL_range_bad(self):
695 headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
696 target = self.public_url + "/foo/new.txt"
697 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad",
698 "501 Not Implemented",
699 "Content-Range in PUT not yet supported",
700 # (and certainly not for immutable files)
701 self.PUT, target, self.NEWFILE_CONTENTS[1:11],
703 d.addCallback(lambda res:
704 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
707 def test_PUT_NEWFILEURL_mutable(self):
708 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
709 self.NEWFILE_CONTENTS)
710 # TODO: we lose the response code, so we can't check this
711 #self.failUnlessEqual(responsecode, 201)
713 u = uri.from_string_mutable_filenode(res)
714 self.failUnless(u.is_mutable())
715 self.failIf(u.is_readonly())
717 d.addCallback(_check_uri)
718 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
719 d.addCallback(lambda res:
720 self.failUnlessMutableChildContentsAre(self._foo_node,
722 self.NEWFILE_CONTENTS))
725 def test_PUT_NEWFILEURL_mutable_toobig(self):
726 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_mutable_toobig",
727 "413 Request Entity Too Large",
728 "SDMF is limited to one segment, and 10001 > 10000",
730 self.public_url + "/foo/new.txt?mutable=true",
731 "b" * (self.s.MUTABLE_SIZELIMIT+1))
734 def test_PUT_NEWFILEURL_replace(self):
735 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
736 # TODO: we lose the response code, so we can't check this
737 #self.failUnlessEqual(responsecode, 200)
738 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
739 d.addCallback(lambda res:
740 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
741 self.NEWFILE_CONTENTS))
744 def test_PUT_NEWFILEURL_bad_t(self):
745 d = self.shouldFail2(error.Error, "PUT_bad_t", "400 Bad Request",
746 "PUT to a file: bad t=bogus",
747 self.PUT, self.public_url + "/foo/bar.txt?t=bogus",
751 def test_PUT_NEWFILEURL_no_replace(self):
752 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
753 self.NEWFILE_CONTENTS)
754 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
756 "There was already a child by that name, and you asked me "
760 def test_PUT_NEWFILEURL_mkdirs(self):
761 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
763 d.addCallback(self.failUnlessURIMatchesChild, fn, u"newdir/new.txt")
764 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
765 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
766 d.addCallback(lambda res:
767 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
768 self.NEWFILE_CONTENTS))
771 def test_PUT_NEWFILEURL_blocked(self):
772 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
773 self.NEWFILE_CONTENTS)
774 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
776 "Unable to create directory 'blockingfile': a file was in the way")
779 def test_DELETE_FILEURL(self):
780 d = self.DELETE(self.public_url + "/foo/bar.txt")
781 d.addCallback(lambda res:
782 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
785 def test_DELETE_FILEURL_missing(self):
786 d = self.DELETE(self.public_url + "/foo/missing")
787 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
790 def test_DELETE_FILEURL_missing2(self):
791 d = self.DELETE(self.public_url + "/missing/missing")
792 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
795 def test_GET_FILEURL_json(self):
796 # twisted.web.http.parse_qs ignores any query args without an '=', so
797 # I can't do "GET /path?json", I have to do "GET /path/t=json"
798 # instead. This may make it tricky to emulate the S3 interface
800 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
801 d.addCallback(self.failUnlessIsBarJSON)
804 def test_GET_FILEURL_json_missing(self):
805 d = self.GET(self.public_url + "/foo/missing?json")
806 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
809 def test_GET_FILEURL_uri(self):
810 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
812 self.failUnlessEqual(res, self._bar_txt_uri)
813 d.addCallback(_check)
814 d.addCallback(lambda res:
815 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
817 # for now, for files, uris and readonly-uris are the same
818 self.failUnlessEqual(res, self._bar_txt_uri)
819 d.addCallback(_check2)
822 def test_GET_FILEURL_badtype(self):
823 d = self.shouldHTTPError2("GET t=bogus", 400, "Bad Request",
826 self.public_url + "/foo/bar.txt?t=bogus")
829 def test_GET_FILEURL_uri_missing(self):
830 d = self.GET(self.public_url + "/foo/missing?t=uri")
831 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
834 def test_GET_DIRURL(self):
835 # the addSlash means we get a redirect here
836 # from /uri/$URI/foo/ , we need ../../../ to get back to the root
838 d = self.GET(self.public_url + "/foo", followRedirect=True)
840 self.failUnless(('<a href="%s">Return to Welcome page' % ROOT)
842 # the FILE reference points to a URI, but it should end in bar.txt
843 bar_url = ("%s/file/%s/@@named=/bar.txt" %
844 (ROOT, urllib.quote(self._bar_txt_uri)))
845 get_bar = "".join([r'<td>',
846 r'<a href="%s">bar.txt</a>' % bar_url,
849 r'\s+<td>%d</td>' % len(self.BAR_CONTENTS),
851 self.failUnless(re.search(get_bar, res), res)
852 for line in res.split("\n"):
853 # find the line that contains the delete button for bar.txt
854 if ("form action" in line and
855 'value="delete"' in line and
856 'value="bar.txt"' in line):
857 # the form target should use a relative URL
858 foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
859 self.failUnless(('action="%s"' % foo_url) in line, line)
860 # and the when_done= should too
861 #done_url = urllib.quote(???)
862 #self.failUnless(('name="when_done" value="%s"' % done_url)
866 self.fail("unable to find delete-bar.txt line", res)
868 # the DIR reference just points to a URI
869 sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
870 get_sub = ((r'<td><a href="%s">sub</a></td>' % sub_url)
871 + r'\s+<td>DIR</td>')
872 self.failUnless(re.search(get_sub, res), res)
873 d.addCallback(_check)
875 # look at a directory which is readonly
876 d.addCallback(lambda res:
877 self.GET(self.public_url + "/reedownlee", followRedirect=True))
879 self.failUnless("(readonly)" in res, res)
880 self.failIf("Upload a file" in res, res)
881 d.addCallback(_check2)
883 # and at a directory that contains a readonly directory
884 d.addCallback(lambda res:
885 self.GET(self.public_url, followRedirect=True))
887 self.failUnless(re.search(r'<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a>'
888 '</td>\s+<td>DIR-RO</td>', res))
889 d.addCallback(_check3)
893 def test_GET_DIRURL_badtype(self):
894 d = self.shouldHTTPError2("test_GET_DIRURL_badtype",
898 self.public_url + "/foo?t=bogus")
901 def test_GET_DIRURL_json(self):
902 d = self.GET(self.public_url + "/foo?t=json")
903 d.addCallback(self.failUnlessIsFooJSON)
907 def test_POST_DIRURL_manifest_no_ophandle(self):
908 d = self.shouldFail2(error.Error,
909 "test_POST_DIRURL_manifest_no_ophandle",
911 "slow operation requires ophandle=",
912 self.POST, self.public_url, t="start-manifest")
915 def test_POST_DIRURL_manifest(self):
916 d = defer.succeed(None)
917 def getman(ignored, output):
918 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
920 d.addCallback(self.wait_for_operation, "125")
921 d.addCallback(self.get_operation_results, "125", output)
923 d.addCallback(getman, None)
924 def _got_html(manifest):
925 self.failUnless("Manifest of SI=" in manifest)
926 self.failUnless("<td>sub</td>" in manifest)
927 self.failUnless(self._sub_uri in manifest)
928 self.failUnless("<td>sub/baz.txt</td>" in manifest)
929 d.addCallback(_got_html)
931 # both t=status and unadorned GET should be identical
932 d.addCallback(lambda res: self.GET("/operations/125"))
933 d.addCallback(_got_html)
935 d.addCallback(getman, "html")
936 d.addCallback(_got_html)
937 d.addCallback(getman, "text")
938 def _got_text(manifest):
939 self.failUnless("\nsub " + self._sub_uri + "\n" in manifest)
940 self.failUnless("\nsub/baz.txt URI:CHK:" in manifest)
941 d.addCallback(_got_text)
942 d.addCallback(getman, "JSON")
944 data = res["manifest"]
946 for (path_list, cap) in data:
947 got[tuple(path_list)] = cap
948 self.failUnlessEqual(got[(u"sub",)], self._sub_uri)
949 self.failUnless((u"sub",u"baz.txt") in got)
950 self.failUnless("finished" in res)
951 self.failUnless("origin" in res)
952 self.failUnless("storage-index" in res)
953 self.failUnless("verifycaps" in res)
954 self.failUnless("stats" in res)
955 d.addCallback(_got_json)
958 def test_POST_DIRURL_deepsize_no_ophandle(self):
959 d = self.shouldFail2(error.Error,
960 "test_POST_DIRURL_deepsize_no_ophandle",
962 "slow operation requires ophandle=",
963 self.POST, self.public_url, t="start-deep-size")
966 def test_POST_DIRURL_deepsize(self):
967 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
969 d.addCallback(self.wait_for_operation, "126")
970 d.addCallback(self.get_operation_results, "126", "json")
972 self.failUnlessEqual(data["finished"], True)
974 self.failUnless(size > 1000)
975 d.addCallback(_got_json)
976 d.addCallback(self.get_operation_results, "126", "text")
978 mo = re.search(r'^size: (\d+)$', res, re.M)
979 self.failUnless(mo, res)
980 size = int(mo.group(1))
981 # with directories, the size varies.
982 self.failUnless(size > 1000)
983 d.addCallback(_got_text)
986 def test_POST_DIRURL_deepstats_no_ophandle(self):
987 d = self.shouldFail2(error.Error,
988 "test_POST_DIRURL_deepstats_no_ophandle",
990 "slow operation requires ophandle=",
991 self.POST, self.public_url, t="start-deep-stats")
994 def test_POST_DIRURL_deepstats(self):
995 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
997 d.addCallback(self.wait_for_operation, "127")
998 d.addCallback(self.get_operation_results, "127", "json")
999 def _got_json(stats):
1000 expected = {"count-immutable-files": 3,
1001 "count-mutable-files": 0,
1002 "count-literal-files": 0,
1004 "count-directories": 3,
1005 "size-immutable-files": 57,
1006 "size-literal-files": 0,
1007 #"size-directories": 1912, # varies
1008 #"largest-directory": 1590,
1009 "largest-directory-children": 5,
1010 "largest-immutable-file": 19,
1012 for k,v in expected.iteritems():
1013 self.failUnlessEqual(stats[k], v,
1014 "stats[%s] was %s, not %s" %
1016 self.failUnlessEqual(stats["size-files-histogram"],
1018 d.addCallback(_got_json)
1021 def test_GET_DIRURL_uri(self):
1022 d = self.GET(self.public_url + "/foo?t=uri")
1024 self.failUnlessEqual(res, self._foo_uri)
1025 d.addCallback(_check)
1028 def test_GET_DIRURL_readonly_uri(self):
1029 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1031 self.failUnlessEqual(res, self._foo_readonly_uri)
1032 d.addCallback(_check)
1035 def test_PUT_NEWDIRURL(self):
1036 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1037 d.addCallback(lambda res:
1038 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1039 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1040 d.addCallback(self.failUnlessNodeKeysAre, [])
1043 def test_PUT_NEWDIRURL_exists(self):
1044 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1045 d.addCallback(lambda res:
1046 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1047 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1048 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1051 def test_PUT_NEWDIRURL_blocked(self):
1052 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
1053 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
1055 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
1056 d.addCallback(lambda res:
1057 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1058 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1059 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1062 def test_PUT_NEWDIRURL_mkdir_p(self):
1063 d = defer.succeed(None)
1064 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
1065 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
1066 d.addCallback(lambda res: self._foo_node.get(u"mkp"))
1067 def mkdir_p(mkpnode):
1068 url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
1070 def made_subsub(ssuri):
1071 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
1072 d.addCallback(lambda ssnode: self.failUnlessEqual(ssnode.get_uri(), ssuri))
1074 d.addCallback(lambda uri2: self.failUnlessEqual(uri2, ssuri))
1076 d.addCallback(made_subsub)
1078 d.addCallback(mkdir_p)
1081 def test_PUT_NEWDIRURL_mkdirs(self):
1082 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1083 d.addCallback(lambda res:
1084 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1085 d.addCallback(lambda res:
1086 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1087 d.addCallback(lambda res:
1088 self._foo_node.get_child_at_path(u"subdir/newdir"))
1089 d.addCallback(self.failUnlessNodeKeysAre, [])
1092 def test_DELETE_DIRURL(self):
1093 d = self.DELETE(self.public_url + "/foo")
1094 d.addCallback(lambda res:
1095 self.failIfNodeHasChild(self.public_root, u"foo"))
1098 def test_DELETE_DIRURL_missing(self):
1099 d = self.DELETE(self.public_url + "/foo/missing")
1100 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
1101 d.addCallback(lambda res:
1102 self.failUnlessNodeHasChild(self.public_root, u"foo"))
1105 def test_DELETE_DIRURL_missing2(self):
1106 d = self.DELETE(self.public_url + "/missing")
1107 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
1110 def dump_root(self):
1112 w = webish.DirnodeWalkerMixin()
1113 def visitor(childpath, childnode, metadata):
1115 d = w.walk(self.public_root, visitor)
1118 def failUnlessNodeKeysAre(self, node, expected_keys):
1119 for k in expected_keys:
1120 assert isinstance(k, unicode)
1122 def _check(children):
1123 self.failUnlessEqual(sorted(children.keys()), sorted(expected_keys))
1124 d.addCallback(_check)
1126 def failUnlessNodeHasChild(self, node, name):
1127 assert isinstance(name, unicode)
1129 def _check(children):
1130 self.failUnless(name in children)
1131 d.addCallback(_check)
1133 def failIfNodeHasChild(self, node, name):
1134 assert isinstance(name, unicode)
1136 def _check(children):
1137 self.failIf(name in children)
1138 d.addCallback(_check)
1141 def failUnlessChildContentsAre(self, node, name, expected_contents):
1142 assert isinstance(name, unicode)
1143 d = node.get_child_at_path(name)
1144 d.addCallback(lambda node: node.download_to_data())
1145 def _check(contents):
1146 self.failUnlessEqual(contents, expected_contents)
1147 d.addCallback(_check)
1150 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
1151 assert isinstance(name, unicode)
1152 d = node.get_child_at_path(name)
1153 d.addCallback(lambda node: node.download_best_version())
1154 def _check(contents):
1155 self.failUnlessEqual(contents, expected_contents)
1156 d.addCallback(_check)
1159 def failUnlessChildURIIs(self, node, name, expected_uri):
1160 assert isinstance(name, unicode)
1161 d = node.get_child_at_path(name)
1163 self.failUnlessEqual(child.get_uri(), expected_uri.strip())
1164 d.addCallback(_check)
1167 def failUnlessURIMatchesChild(self, got_uri, node, name):
1168 assert isinstance(name, unicode)
1169 d = node.get_child_at_path(name)
1171 self.failUnlessEqual(got_uri.strip(), child.get_uri())
1172 d.addCallback(_check)
1175 def failUnlessCHKURIHasContents(self, got_uri, contents):
1176 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
1178 def test_POST_upload(self):
1179 d = self.POST(self.public_url + "/foo", t="upload",
1180 file=("new.txt", self.NEWFILE_CONTENTS))
1182 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1183 d.addCallback(lambda res:
1184 self.failUnlessChildContentsAre(fn, u"new.txt",
1185 self.NEWFILE_CONTENTS))
1188 def test_POST_upload_unicode(self):
1189 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1190 d = self.POST(self.public_url + "/foo", t="upload",
1191 file=(filename, self.NEWFILE_CONTENTS))
1193 d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1194 d.addCallback(lambda res:
1195 self.failUnlessChildContentsAre(fn, filename,
1196 self.NEWFILE_CONTENTS))
1197 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1198 d.addCallback(lambda res: self.GET(target_url))
1199 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1200 self.NEWFILE_CONTENTS,
1204 def test_POST_upload_unicode_named(self):
1205 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1206 d = self.POST(self.public_url + "/foo", t="upload",
1208 file=("overridden", self.NEWFILE_CONTENTS))
1210 d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1211 d.addCallback(lambda res:
1212 self.failUnlessChildContentsAre(fn, filename,
1213 self.NEWFILE_CONTENTS))
1214 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1215 d.addCallback(lambda res: self.GET(target_url))
1216 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1217 self.NEWFILE_CONTENTS,
1221 def test_POST_upload_no_link(self):
1222 d = self.POST("/uri", t="upload",
1223 file=("new.txt", self.NEWFILE_CONTENTS))
1224 def _check_upload_results(page):
1225 # this should be a page which describes the results of the upload
1226 # that just finished.
1227 self.failUnless("Upload Results:" in page)
1228 self.failUnless("URI:" in page)
1229 uri_re = re.compile("URI: <tt><span>(.*)</span>")
1230 mo = uri_re.search(page)
1231 self.failUnless(mo, page)
1232 new_uri = mo.group(1)
1234 d.addCallback(_check_upload_results)
1235 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1238 def test_POST_upload_no_link_whendone(self):
1239 d = self.POST("/uri", t="upload", when_done="/",
1240 file=("new.txt", self.NEWFILE_CONTENTS))
1241 d.addBoth(self.shouldRedirect, "/")
1244 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
1245 d = defer.maybeDeferred(callable, *args, **kwargs)
1247 if isinstance(res, failure.Failure):
1248 res.trap(error.PageRedirect)
1249 statuscode = res.value.status
1250 target = res.value.location
1251 return checker(statuscode, target)
1252 self.fail("%s: callable was supposed to redirect, not return '%s'"
1257 def test_POST_upload_no_link_whendone_results(self):
1258 def check(statuscode, target):
1259 self.failUnlessEqual(statuscode, str(http.FOUND))
1260 self.failUnless(target.startswith(self.webish_url), target)
1261 return client.getPage(target, method="GET")
1262 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
1264 self.POST, "/uri", t="upload",
1265 when_done="/uri/%(uri)s",
1266 file=("new.txt", self.NEWFILE_CONTENTS))
1267 d.addCallback(lambda res:
1268 self.failUnlessEqual(res, self.NEWFILE_CONTENTS))
1271 def test_POST_upload_no_link_mutable(self):
1272 d = self.POST("/uri", t="upload", mutable="true",
1273 file=("new.txt", self.NEWFILE_CONTENTS))
1274 def _check(new_uri):
1275 new_uri = new_uri.strip()
1276 self.new_uri = new_uri
1278 self.failUnless(IMutableFileURI.providedBy(u))
1279 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
1280 n = self.s.create_node_from_uri(new_uri)
1281 return n.download_best_version()
1282 d.addCallback(_check)
1284 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1285 return self.GET("/uri/%s" % urllib.quote(self.new_uri))
1286 d.addCallback(_check2)
1288 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1289 return self.GET("/file/%s" % urllib.quote(self.new_uri))
1290 d.addCallback(_check3)
1292 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1293 d.addCallback(_check4)
1296 def test_POST_upload_no_link_mutable_toobig(self):
1297 d = self.shouldFail2(error.Error,
1298 "test_POST_upload_no_link_mutable_toobig",
1299 "413 Request Entity Too Large",
1300 "SDMF is limited to one segment, and 10001 > 10000",
1302 "/uri", t="upload", mutable="true",
1304 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1307 def test_POST_upload_mutable(self):
1308 # this creates a mutable file
1309 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
1310 file=("new.txt", self.NEWFILE_CONTENTS))
1312 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1313 d.addCallback(lambda res:
1314 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1315 self.NEWFILE_CONTENTS))
1316 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1318 self.failUnless(IMutableFileNode.providedBy(newnode))
1319 self.failUnless(newnode.is_mutable())
1320 self.failIf(newnode.is_readonly())
1321 self._mutable_node = newnode
1322 self._mutable_uri = newnode.get_uri()
1325 # now upload it again and make sure that the URI doesn't change
1326 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
1327 d.addCallback(lambda res:
1328 self.POST(self.public_url + "/foo", t="upload",
1330 file=("new.txt", NEWER_CONTENTS)))
1331 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1332 d.addCallback(lambda res:
1333 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1335 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1337 self.failUnless(IMutableFileNode.providedBy(newnode))
1338 self.failUnless(newnode.is_mutable())
1339 self.failIf(newnode.is_readonly())
1340 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1341 d.addCallback(_got2)
1343 # upload a second time, using PUT instead of POST
1344 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
1345 d.addCallback(lambda res:
1346 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
1347 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1348 d.addCallback(lambda res:
1349 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1352 # finally list the directory, since mutable files are displayed
1353 # slightly differently
1355 d.addCallback(lambda res:
1356 self.GET(self.public_url + "/foo/",
1357 followRedirect=True))
1358 def _check_page(res):
1359 # TODO: assert more about the contents
1360 self.failUnless("SSK" in res)
1362 d.addCallback(_check_page)
1364 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1366 self.failUnless(IMutableFileNode.providedBy(newnode))
1367 self.failUnless(newnode.is_mutable())
1368 self.failIf(newnode.is_readonly())
1369 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1370 d.addCallback(_got3)
1372 # look at the JSON form of the enclosing directory
1373 d.addCallback(lambda res:
1374 self.GET(self.public_url + "/foo/?t=json",
1375 followRedirect=True))
1376 def _check_page_json(res):
1377 parsed = simplejson.loads(res)
1378 self.failUnlessEqual(parsed[0], "dirnode")
1379 children = dict( [(unicode(name),value)
1381 in parsed[1]["children"].iteritems()] )
1382 self.failUnless("new.txt" in children)
1383 new_json = children["new.txt"]
1384 self.failUnlessEqual(new_json[0], "filenode")
1385 self.failUnless(new_json[1]["mutable"])
1386 self.failUnlessEqual(new_json[1]["rw_uri"], self._mutable_uri)
1387 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1388 self.failUnlessEqual(new_json[1]["ro_uri"], ro_uri)
1389 d.addCallback(_check_page_json)
1391 # and the JSON form of the file
1392 d.addCallback(lambda res:
1393 self.GET(self.public_url + "/foo/new.txt?t=json"))
1394 def _check_file_json(res):
1395 parsed = simplejson.loads(res)
1396 self.failUnlessEqual(parsed[0], "filenode")
1397 self.failUnless(parsed[1]["mutable"])
1398 self.failUnlessEqual(parsed[1]["rw_uri"], self._mutable_uri)
1399 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1400 self.failUnlessEqual(parsed[1]["ro_uri"], ro_uri)
1401 d.addCallback(_check_file_json)
1403 # and look at t=uri and t=readonly-uri
1404 d.addCallback(lambda res:
1405 self.GET(self.public_url + "/foo/new.txt?t=uri"))
1406 d.addCallback(lambda res: self.failUnlessEqual(res, self._mutable_uri))
1407 d.addCallback(lambda res:
1408 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
1409 def _check_ro_uri(res):
1410 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1411 self.failUnlessEqual(res, ro_uri)
1412 d.addCallback(_check_ro_uri)
1414 # make sure we can get to it from /uri/URI
1415 d.addCallback(lambda res:
1416 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
1417 d.addCallback(lambda res:
1418 self.failUnlessEqual(res, NEW2_CONTENTS))
1420 # and that HEAD computes the size correctly
1421 d.addCallback(lambda res:
1422 self.HEAD(self.public_url + "/foo/new.txt",
1423 return_response=True))
1424 def _got_headers((res, status, headers)):
1425 self.failUnlessEqual(res, "")
1426 self.failUnlessEqual(headers["content-length"][0],
1427 str(len(NEW2_CONTENTS)))
1428 self.failUnlessEqual(headers["content-type"], ["text/plain"])
1429 d.addCallback(_got_headers)
1431 # make sure that size errors are displayed correctly for overwrite
1432 d.addCallback(lambda res:
1433 self.shouldFail2(error.Error,
1434 "test_POST_upload_mutable-toobig",
1435 "413 Request Entity Too Large",
1436 "SDMF is limited to one segment, and 10001 > 10000",
1438 self.public_url + "/foo", t="upload",
1441 "b" * (self.s.MUTABLE_SIZELIMIT+1)),
1444 d.addErrback(self.dump_error)
1447 def test_POST_upload_mutable_toobig(self):
1448 d = self.shouldFail2(error.Error,
1449 "test_POST_upload_no_link_mutable_toobig",
1450 "413 Request Entity Too Large",
1451 "SDMF is limited to one segment, and 10001 > 10000",
1453 self.public_url + "/foo",
1454 t="upload", mutable="true",
1456 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1459 def dump_error(self, f):
1460 # if the web server returns an error code (like 400 Bad Request),
1461 # web.client.getPage puts the HTTP response body into the .response
1462 # attribute of the exception object that it gives back. It does not
1463 # appear in the Failure's repr(), so the ERROR that trial displays
1464 # will be rather terse and unhelpful. addErrback this method to the
1465 # end of your chain to get more information out of these errors.
1466 if f.check(error.Error):
1467 print "web.error.Error:"
1469 print f.value.response
1472 def test_POST_upload_replace(self):
1473 d = self.POST(self.public_url + "/foo", t="upload",
1474 file=("bar.txt", self.NEWFILE_CONTENTS))
1476 d.addCallback(self.failUnlessURIMatchesChild, fn, u"bar.txt")
1477 d.addCallback(lambda res:
1478 self.failUnlessChildContentsAre(fn, u"bar.txt",
1479 self.NEWFILE_CONTENTS))
1482 def test_POST_upload_no_replace_ok(self):
1483 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1484 file=("new.txt", self.NEWFILE_CONTENTS))
1485 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
1486 d.addCallback(lambda res: self.failUnlessEqual(res,
1487 self.NEWFILE_CONTENTS))
1490 def test_POST_upload_no_replace_queryarg(self):
1491 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1492 file=("bar.txt", self.NEWFILE_CONTENTS))
1493 d.addBoth(self.shouldFail, error.Error,
1494 "POST_upload_no_replace_queryarg",
1496 "There was already a child by that name, and you asked me "
1497 "to not replace it")
1498 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1499 d.addCallback(self.failUnlessIsBarDotTxt)
1502 def test_POST_upload_no_replace_field(self):
1503 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
1504 file=("bar.txt", self.NEWFILE_CONTENTS))
1505 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
1507 "There was already a child by that name, and you asked me "
1508 "to not replace it")
1509 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1510 d.addCallback(self.failUnlessIsBarDotTxt)
1513 def test_POST_upload_whendone(self):
1514 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
1515 file=("new.txt", self.NEWFILE_CONTENTS))
1516 d.addBoth(self.shouldRedirect, "/THERE")
1518 d.addCallback(lambda res:
1519 self.failUnlessChildContentsAre(fn, u"new.txt",
1520 self.NEWFILE_CONTENTS))
1523 def test_POST_upload_named(self):
1525 d = self.POST(self.public_url + "/foo", t="upload",
1526 name="new.txt", file=self.NEWFILE_CONTENTS)
1527 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1528 d.addCallback(lambda res:
1529 self.failUnlessChildContentsAre(fn, u"new.txt",
1530 self.NEWFILE_CONTENTS))
1533 def test_POST_upload_named_badfilename(self):
1534 d = self.POST(self.public_url + "/foo", t="upload",
1535 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
1536 d.addBoth(self.shouldFail, error.Error,
1537 "test_POST_upload_named_badfilename",
1539 "name= may not contain a slash",
1541 # make sure that nothing was added
1542 d.addCallback(lambda res:
1543 self.failUnlessNodeKeysAre(self._foo_node,
1544 [u"bar.txt", u"blockingfile",
1545 u"empty", u"n\u00fc.txt",
1549 def test_POST_FILEURL_check(self):
1550 bar_url = self.public_url + "/foo/bar.txt"
1551 d = self.POST(bar_url, t="check")
1553 self.failUnless("Healthy :" in res)
1554 d.addCallback(_check)
1555 redir_url = "http://allmydata.org/TARGET"
1556 def _check2(statuscode, target):
1557 self.failUnlessEqual(statuscode, str(http.FOUND))
1558 self.failUnlessEqual(target, redir_url)
1559 d.addCallback(lambda res:
1560 self.shouldRedirect2("test_POST_FILEURL_check",
1564 when_done=redir_url))
1565 d.addCallback(lambda res:
1566 self.POST(bar_url, t="check", return_to=redir_url))
1568 self.failUnless("Healthy :" in res)
1569 self.failUnless("Return to parent directory" in res)
1570 self.failUnless(redir_url in res)
1571 d.addCallback(_check3)
1573 d.addCallback(lambda res:
1574 self.POST(bar_url, t="check", output="JSON"))
1575 def _check_json(res):
1576 data = simplejson.loads(res)
1577 self.failUnless("storage-index" in data)
1578 self.failUnless(data["results"]["healthy"])
1579 d.addCallback(_check_json)
1583 def test_POST_FILEURL_check_and_repair(self):
1584 bar_url = self.public_url + "/foo/bar.txt"
1585 d = self.POST(bar_url, t="check", repair="true")
1587 self.failUnless("Healthy :" in res)
1588 d.addCallback(_check)
1589 redir_url = "http://allmydata.org/TARGET"
1590 def _check2(statuscode, target):
1591 self.failUnlessEqual(statuscode, str(http.FOUND))
1592 self.failUnlessEqual(target, redir_url)
1593 d.addCallback(lambda res:
1594 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
1597 t="check", repair="true",
1598 when_done=redir_url))
1599 d.addCallback(lambda res:
1600 self.POST(bar_url, t="check", return_to=redir_url))
1602 self.failUnless("Healthy :" in res)
1603 self.failUnless("Return to parent directory" in res)
1604 self.failUnless(redir_url in res)
1605 d.addCallback(_check3)
1608 def test_POST_DIRURL_check(self):
1609 foo_url = self.public_url + "/foo/"
1610 d = self.POST(foo_url, t="check")
1612 self.failUnless("Healthy :" in res, res)
1613 d.addCallback(_check)
1614 redir_url = "http://allmydata.org/TARGET"
1615 def _check2(statuscode, target):
1616 self.failUnlessEqual(statuscode, str(http.FOUND))
1617 self.failUnlessEqual(target, redir_url)
1618 d.addCallback(lambda res:
1619 self.shouldRedirect2("test_POST_DIRURL_check",
1623 when_done=redir_url))
1624 d.addCallback(lambda res:
1625 self.POST(foo_url, t="check", return_to=redir_url))
1627 self.failUnless("Healthy :" in res, res)
1628 self.failUnless("Return to parent directory" in res)
1629 self.failUnless(redir_url in res)
1630 d.addCallback(_check3)
1632 d.addCallback(lambda res:
1633 self.POST(foo_url, t="check", output="JSON"))
1634 def _check_json(res):
1635 data = simplejson.loads(res)
1636 self.failUnless("storage-index" in data)
1637 self.failUnless(data["results"]["healthy"])
1638 d.addCallback(_check_json)
1642 def test_POST_DIRURL_check_and_repair(self):
1643 foo_url = self.public_url + "/foo/"
1644 d = self.POST(foo_url, t="check", repair="true")
1646 self.failUnless("Healthy :" in res, res)
1647 d.addCallback(_check)
1648 redir_url = "http://allmydata.org/TARGET"
1649 def _check2(statuscode, target):
1650 self.failUnlessEqual(statuscode, str(http.FOUND))
1651 self.failUnlessEqual(target, redir_url)
1652 d.addCallback(lambda res:
1653 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
1656 t="check", repair="true",
1657 when_done=redir_url))
1658 d.addCallback(lambda res:
1659 self.POST(foo_url, t="check", return_to=redir_url))
1661 self.failUnless("Healthy :" in res)
1662 self.failUnless("Return to parent directory" in res)
1663 self.failUnless(redir_url in res)
1664 d.addCallback(_check3)
1667 def wait_for_operation(self, ignored, ophandle):
1668 url = "/operations/" + ophandle
1669 url += "?t=status&output=JSON"
1672 data = simplejson.loads(res)
1673 if not data["finished"]:
1674 d = self.stall(delay=1.0)
1675 d.addCallback(self.wait_for_operation, ophandle)
1681 def get_operation_results(self, ignored, ophandle, output=None):
1682 url = "/operations/" + ophandle
1685 url += "&output=" + output
1688 if output and output.lower() == "json":
1689 return simplejson.loads(res)
1694 def test_POST_DIRURL_deepcheck_no_ophandle(self):
1695 d = self.shouldFail2(error.Error,
1696 "test_POST_DIRURL_deepcheck_no_ophandle",
1698 "slow operation requires ophandle=",
1699 self.POST, self.public_url, t="start-deep-check")
1702 def test_POST_DIRURL_deepcheck(self):
1703 def _check_redirect(statuscode, target):
1704 self.failUnlessEqual(statuscode, str(http.FOUND))
1705 self.failUnless(target.endswith("/operations/123"))
1706 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
1707 self.POST, self.public_url,
1708 t="start-deep-check", ophandle="123")
1709 d.addCallback(self.wait_for_operation, "123")
1710 def _check_json(data):
1711 self.failUnlessEqual(data["finished"], True)
1712 self.failUnlessEqual(data["count-objects-checked"], 8)
1713 self.failUnlessEqual(data["count-objects-healthy"], 8)
1714 d.addCallback(_check_json)
1715 d.addCallback(self.get_operation_results, "123", "html")
1716 def _check_html(res):
1717 self.failUnless("Objects Checked: <span>8</span>" in res)
1718 self.failUnless("Objects Healthy: <span>8</span>" in res)
1719 d.addCallback(_check_html)
1721 d.addCallback(lambda res:
1722 self.GET("/operations/123/"))
1723 d.addCallback(_check_html) # should be the same as without the slash
1725 d.addCallback(lambda res:
1726 self.shouldFail2(error.Error, "one", "404 Not Found",
1727 "No detailed results for SI bogus",
1728 self.GET, "/operations/123/bogus"))
1730 foo_si = self._foo_node.get_storage_index()
1731 foo_si_s = base32.b2a(foo_si)
1732 d.addCallback(lambda res:
1733 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
1734 def _check_foo_json(res):
1735 data = simplejson.loads(res)
1736 self.failUnlessEqual(data["storage-index"], foo_si_s)
1737 self.failUnless(data["results"]["healthy"])
1738 d.addCallback(_check_foo_json)
1741 def test_POST_DIRURL_deepcheck_and_repair(self):
1742 d = self.POST(self.public_url, t="start-deep-check", repair="true",
1743 ophandle="124", output="json", followRedirect=True)
1744 d.addCallback(self.wait_for_operation, "124")
1745 def _check_json(data):
1746 self.failUnlessEqual(data["finished"], True)
1747 self.failUnlessEqual(data["count-objects-checked"], 8)
1748 self.failUnlessEqual(data["count-objects-healthy-pre-repair"], 8)
1749 self.failUnlessEqual(data["count-objects-unhealthy-pre-repair"], 0)
1750 self.failUnlessEqual(data["count-corrupt-shares-pre-repair"], 0)
1751 self.failUnlessEqual(data["count-repairs-attempted"], 0)
1752 self.failUnlessEqual(data["count-repairs-successful"], 0)
1753 self.failUnlessEqual(data["count-repairs-unsuccessful"], 0)
1754 self.failUnlessEqual(data["count-objects-healthy-post-repair"], 8)
1755 self.failUnlessEqual(data["count-objects-unhealthy-post-repair"], 0)
1756 self.failUnlessEqual(data["count-corrupt-shares-post-repair"], 0)
1757 d.addCallback(_check_json)
1758 d.addCallback(self.get_operation_results, "124", "html")
1759 def _check_html(res):
1760 self.failUnless("Objects Checked: <span>8</span>" in res)
1762 self.failUnless("Objects Healthy (before repair): <span>8</span>" in res)
1763 self.failUnless("Objects Unhealthy (before repair): <span>0</span>" in res)
1764 self.failUnless("Corrupt Shares (before repair): <span>0</span>" in res)
1766 self.failUnless("Repairs Attempted: <span>0</span>" in res)
1767 self.failUnless("Repairs Successful: <span>0</span>" in res)
1768 self.failUnless("Repairs Unsuccessful: <span>0</span>" in res)
1770 self.failUnless("Objects Healthy (after repair): <span>8</span>" in res)
1771 self.failUnless("Objects Unhealthy (after repair): <span>0</span>" in res)
1772 self.failUnless("Corrupt Shares (after repair): <span>0</span>" in res)
1773 d.addCallback(_check_html)
1776 def test_POST_FILEURL_bad_t(self):
1777 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
1778 "POST to file: bad t=bogus",
1779 self.POST, self.public_url + "/foo/bar.txt",
1783 def test_POST_mkdir(self): # return value?
1784 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
1785 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1786 d.addCallback(self.failUnlessNodeKeysAre, [])
1789 def test_POST_mkdir_2(self):
1790 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
1791 d.addCallback(lambda res:
1792 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1793 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1794 d.addCallback(self.failUnlessNodeKeysAre, [])
1797 def test_POST_mkdirs_2(self):
1798 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
1799 d.addCallback(lambda res:
1800 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
1801 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
1802 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
1803 d.addCallback(self.failUnlessNodeKeysAre, [])
1806 def test_POST_mkdir_no_parentdir_noredirect(self):
1807 d = self.POST("/uri?t=mkdir")
1808 def _after_mkdir(res):
1809 uri.NewDirectoryURI.init_from_string(res)
1810 d.addCallback(_after_mkdir)
1813 def test_POST_mkdir_no_parentdir_redirect(self):
1814 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
1815 d.addBoth(self.shouldRedirect, None, statuscode='303')
1816 def _check_target(target):
1817 target = urllib.unquote(target)
1818 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
1819 d.addCallback(_check_target)
1822 def test_POST_noparent_bad(self):
1823 d = self.shouldHTTPError2("POST /uri?t=bogus", 400, "Bad Request",
1824 "/uri accepts only PUT, PUT?t=mkdir, "
1825 "POST?t=upload, and POST?t=mkdir",
1826 self.POST, "/uri?t=bogus")
1829 def test_welcome_page_mkdir_button(self):
1830 # Fetch the welcome page.
1832 def _after_get_welcome_page(res):
1833 MKDIR_BUTTON_RE=re.compile('<form action="([^"]*)" method="post".*<input type="hidden" name="t" value="([^"]*)" /><input type="hidden" name="([^"]*)" value="([^"]*)" /><input type="submit" value="Create Directory!" />', re.I)
1834 mo = MKDIR_BUTTON_RE.search(res)
1835 formaction = mo.group(1)
1837 formaname = mo.group(3)
1838 formavalue = mo.group(4)
1839 return (formaction, formt, formaname, formavalue)
1840 d.addCallback(_after_get_welcome_page)
1841 def _after_parse_form(res):
1842 (formaction, formt, formaname, formavalue) = res
1843 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
1844 d.addCallback(_after_parse_form)
1845 d.addBoth(self.shouldRedirect, None, statuscode='303')
1848 def test_POST_mkdir_replace(self): # return value?
1849 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
1850 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1851 d.addCallback(self.failUnlessNodeKeysAre, [])
1854 def test_POST_mkdir_no_replace_queryarg(self): # return value?
1855 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
1856 d.addBoth(self.shouldFail, error.Error,
1857 "POST_mkdir_no_replace_queryarg",
1859 "There was already a child by that name, and you asked me "
1860 "to not replace it")
1861 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1862 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1865 def test_POST_mkdir_no_replace_field(self): # return value?
1866 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
1868 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
1870 "There was already a child by that name, and you asked me "
1871 "to not replace it")
1872 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1873 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1876 def test_POST_mkdir_whendone_field(self):
1877 d = self.POST(self.public_url + "/foo",
1878 t="mkdir", name="newdir", when_done="/THERE")
1879 d.addBoth(self.shouldRedirect, "/THERE")
1880 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1881 d.addCallback(self.failUnlessNodeKeysAre, [])
1884 def test_POST_mkdir_whendone_queryarg(self):
1885 d = self.POST(self.public_url + "/foo?when_done=/THERE",
1886 t="mkdir", name="newdir")
1887 d.addBoth(self.shouldRedirect, "/THERE")
1888 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1889 d.addCallback(self.failUnlessNodeKeysAre, [])
1892 def test_POST_bad_t(self):
1893 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
1894 "POST to a directory with bad t=BOGUS",
1895 self.POST, self.public_url + "/foo", t="BOGUS")
1898 def test_POST_set_children(self):
1899 contents9, n9, newuri9 = self.makefile(9)
1900 contents10, n10, newuri10 = self.makefile(10)
1901 contents11, n11, newuri11 = self.makefile(11)
1904 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
1907 "ctime": 1002777696.7564139,
1908 "mtime": 1002777696.7564139
1911 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
1914 "ctime": 1002777696.7564139,
1915 "mtime": 1002777696.7564139
1918 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
1921 "ctime": 1002777696.7564139,
1922 "mtime": 1002777696.7564139
1925 }""" % (newuri9, newuri10, newuri11)
1927 url = self.webish_url + self.public_url + "/foo" + "?t=set_children"
1929 d = client.getPage(url, method="POST", postdata=reqbody)
1931 self.failUnlessURIMatchesChild(newuri9, self._foo_node, u"atomic_added_1")
1932 self.failUnlessURIMatchesChild(newuri10, self._foo_node, u"atomic_added_2")
1933 self.failUnlessURIMatchesChild(newuri11, self._foo_node, u"atomic_added_3")
1935 d.addCallback(_then)
1936 d.addErrback(self.dump_error)
1939 def test_POST_put_uri(self):
1940 contents, n, newuri = self.makefile(8)
1941 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
1942 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
1943 d.addCallback(lambda res:
1944 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1948 def test_POST_put_uri_replace(self):
1949 contents, n, newuri = self.makefile(8)
1950 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
1951 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
1952 d.addCallback(lambda res:
1953 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1957 def test_POST_put_uri_no_replace_queryarg(self):
1958 contents, n, newuri = self.makefile(8)
1959 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
1960 name="bar.txt", uri=newuri)
1961 d.addBoth(self.shouldFail, error.Error,
1962 "POST_put_uri_no_replace_queryarg",
1964 "There was already a child by that name, and you asked me "
1965 "to not replace it")
1966 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1967 d.addCallback(self.failUnlessIsBarDotTxt)
1970 def test_POST_put_uri_no_replace_field(self):
1971 contents, n, newuri = self.makefile(8)
1972 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
1973 name="bar.txt", uri=newuri)
1974 d.addBoth(self.shouldFail, error.Error,
1975 "POST_put_uri_no_replace_field",
1977 "There was already a child by that name, and you asked me "
1978 "to not replace it")
1979 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1980 d.addCallback(self.failUnlessIsBarDotTxt)
1983 def test_POST_delete(self):
1984 d = self.POST(self.public_url + "/foo", t="delete", name="bar.txt")
1985 d.addCallback(lambda res: self._foo_node.list())
1986 def _check(children):
1987 self.failIf(u"bar.txt" in children)
1988 d.addCallback(_check)
1991 def test_POST_rename_file(self):
1992 d = self.POST(self.public_url + "/foo", t="rename",
1993 from_name="bar.txt", to_name='wibble.txt')
1994 d.addCallback(lambda res:
1995 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1996 d.addCallback(lambda res:
1997 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
1998 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
1999 d.addCallback(self.failUnlessIsBarDotTxt)
2000 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
2001 d.addCallback(self.failUnlessIsBarJSON)
2004 def test_POST_rename_file_redundant(self):
2005 d = self.POST(self.public_url + "/foo", t="rename",
2006 from_name="bar.txt", to_name='bar.txt')
2007 d.addCallback(lambda res:
2008 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2009 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2010 d.addCallback(self.failUnlessIsBarDotTxt)
2011 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
2012 d.addCallback(self.failUnlessIsBarJSON)
2015 def test_POST_rename_file_replace(self):
2016 # rename a file and replace a directory with it
2017 d = self.POST(self.public_url + "/foo", t="rename",
2018 from_name="bar.txt", to_name='empty')
2019 d.addCallback(lambda res:
2020 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
2021 d.addCallback(lambda res:
2022 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
2023 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
2024 d.addCallback(self.failUnlessIsBarDotTxt)
2025 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2026 d.addCallback(self.failUnlessIsBarJSON)
2029 def test_POST_rename_file_no_replace_queryarg(self):
2030 # rename a file and replace a directory with it
2031 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
2032 from_name="bar.txt", to_name='empty')
2033 d.addBoth(self.shouldFail, error.Error,
2034 "POST_rename_file_no_replace_queryarg",
2036 "There was already a child by that name, and you asked me "
2037 "to not replace it")
2038 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2039 d.addCallback(self.failUnlessIsEmptyJSON)
2042 def test_POST_rename_file_no_replace_field(self):
2043 # rename a file and replace a directory with it
2044 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
2045 from_name="bar.txt", to_name='empty')
2046 d.addBoth(self.shouldFail, error.Error,
2047 "POST_rename_file_no_replace_field",
2049 "There was already a child by that name, and you asked me "
2050 "to not replace it")
2051 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2052 d.addCallback(self.failUnlessIsEmptyJSON)
2055 def failUnlessIsEmptyJSON(self, res):
2056 data = simplejson.loads(res)
2057 self.failUnlessEqual(data[0], "dirnode", data)
2058 self.failUnlessEqual(len(data[1]["children"]), 0)
2060 def test_POST_rename_file_slash_fail(self):
2061 d = self.POST(self.public_url + "/foo", t="rename",
2062 from_name="bar.txt", to_name='kirk/spock.txt')
2063 d.addBoth(self.shouldFail, error.Error,
2064 "test_POST_rename_file_slash_fail",
2066 "to_name= may not contain a slash",
2068 d.addCallback(lambda res:
2069 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2072 def test_POST_rename_dir(self):
2073 d = self.POST(self.public_url, t="rename",
2074 from_name="foo", to_name='plunk')
2075 d.addCallback(lambda res:
2076 self.failIfNodeHasChild(self.public_root, u"foo"))
2077 d.addCallback(lambda res:
2078 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
2079 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
2080 d.addCallback(self.failUnlessIsFooJSON)
2083 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
2084 """ If target is not None then the redirection has to go to target. If
2085 statuscode is not None then the redirection has to be accomplished with
2086 that HTTP status code."""
2087 if not isinstance(res, failure.Failure):
2088 to_where = (target is None) and "somewhere" or ("to " + target)
2089 self.fail("%s: we were expecting to get redirected %s, not get an"
2090 " actual page: %s" % (which, to_where, res))
2091 res.trap(error.PageRedirect)
2092 if statuscode is not None:
2093 self.failUnlessEqual(res.value.status, statuscode,
2094 "%s: not a redirect" % which)
2095 if target is not None:
2096 # the PageRedirect does not seem to capture the uri= query arg
2097 # properly, so we can't check for it.
2098 realtarget = self.webish_url + target
2099 self.failUnlessEqual(res.value.location, realtarget,
2100 "%s: wrong target" % which)
2101 return res.value.location
2103 def test_GET_URI_form(self):
2104 base = "/uri?uri=%s" % self._bar_txt_uri
2105 # this is supposed to give us a redirect to /uri/$URI, plus arguments
2106 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
2108 d.addBoth(self.shouldRedirect, targetbase)
2109 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
2110 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
2111 d.addCallback(lambda res: self.GET(base+"&t=json"))
2112 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
2113 d.addCallback(self.log, "about to get file by uri")
2114 d.addCallback(lambda res: self.GET(base, followRedirect=True))
2115 d.addCallback(self.failUnlessIsBarDotTxt)
2116 d.addCallback(self.log, "got file by uri, about to get dir by uri")
2117 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
2118 followRedirect=True))
2119 d.addCallback(self.failUnlessIsFooJSON)
2120 d.addCallback(self.log, "got dir by uri")
2124 def test_GET_URI_form_bad(self):
2125 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
2126 "400 Bad Request", "GET /uri requires uri=",
2130 def test_GET_rename_form(self):
2131 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
2132 followRedirect=True)
2134 self.failUnless('name="when_done" value="."' in res, res)
2135 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
2136 d.addCallback(_check)
2139 def log(self, res, msg):
2140 #print "MSG: %s RES: %s" % (msg, res)
2144 def test_GET_URI_URL(self):
2145 base = "/uri/%s" % self._bar_txt_uri
2147 d.addCallback(self.failUnlessIsBarDotTxt)
2148 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
2149 d.addCallback(self.failUnlessIsBarDotTxt)
2150 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
2151 d.addCallback(self.failUnlessIsBarDotTxt)
2154 def test_GET_URI_URL_dir(self):
2155 base = "/uri/%s?t=json" % self._foo_uri
2157 d.addCallback(self.failUnlessIsFooJSON)
2160 def test_GET_URI_URL_missing(self):
2161 base = "/uri/%s" % self._bad_file_uri
2163 d.addBoth(self.shouldHTTPError, "test_GET_URI_URL_missing",
2164 http.GONE, response_substring="NotEnoughSharesError")
2165 # TODO: how can we exercise both sides of WebDownloadTarget.fail
2166 # here? we must arrange for a download to fail after target.open()
2167 # has been called, and then inspect the response to see that it is
2168 # shorter than we expected.
2171 def test_PUT_DIRURL_uri(self):
2172 d = self.s.create_empty_dirnode()
2174 new_uri = dn.get_uri()
2175 # replace /foo with a new (empty) directory
2176 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
2177 d.addCallback(lambda res:
2178 self.failUnlessEqual(res.strip(), new_uri))
2179 d.addCallback(lambda res:
2180 self.failUnlessChildURIIs(self.public_root,
2184 d.addCallback(_made_dir)
2187 def test_PUT_DIRURL_uri_noreplace(self):
2188 d = self.s.create_empty_dirnode()
2190 new_uri = dn.get_uri()
2191 # replace /foo with a new (empty) directory, but ask that
2192 # replace=false, so it should fail
2193 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
2194 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
2196 self.public_url + "/foo?t=uri&replace=false",
2198 d.addCallback(lambda res:
2199 self.failUnlessChildURIIs(self.public_root,
2203 d.addCallback(_made_dir)
2206 def test_PUT_DIRURL_bad_t(self):
2207 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
2208 "400 Bad Request", "PUT to a directory",
2209 self.PUT, self.public_url + "/foo?t=BOGUS", "")
2210 d.addCallback(lambda res:
2211 self.failUnlessChildURIIs(self.public_root,
2216 def test_PUT_NEWFILEURL_uri(self):
2217 contents, n, new_uri = self.makefile(8)
2218 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
2219 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2220 d.addCallback(lambda res:
2221 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
2225 def test_PUT_NEWFILEURL_uri_replace(self):
2226 contents, n, new_uri = self.makefile(8)
2227 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
2228 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2229 d.addCallback(lambda res:
2230 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
2234 def test_PUT_NEWFILEURL_uri_no_replace(self):
2235 contents, n, new_uri = self.makefile(8)
2236 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
2237 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_uri_no_replace",
2239 "There was already a child by that name, and you asked me "
2240 "to not replace it")
2243 def test_PUT_NEWFILE_URI(self):
2244 file_contents = "New file contents here\n"
2245 d = self.PUT("/uri", file_contents)
2247 self.failUnless(uri in FakeCHKFileNode.all_contents)
2248 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
2250 return self.GET("/uri/%s" % uri)
2251 d.addCallback(_check)
2253 self.failUnlessEqual(res, file_contents)
2254 d.addCallback(_check2)
2257 def test_PUT_NEWFILE_URI_only_PUT(self):
2258 d = self.PUT("/uri?t=bogus", "")
2259 d.addBoth(self.shouldFail, error.Error,
2260 "PUT_NEWFILE_URI_only_PUT",
2262 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
2265 def test_PUT_NEWFILE_URI_mutable(self):
2266 file_contents = "New file contents here\n"
2267 d = self.PUT("/uri?mutable=true", file_contents)
2268 def _check_mutable(uri):
2271 self.failUnless(IMutableFileURI.providedBy(u))
2272 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
2273 n = self.s.create_node_from_uri(uri)
2274 return n.download_best_version()
2275 d.addCallback(_check_mutable)
2276 def _check2_mutable(data):
2277 self.failUnlessEqual(data, file_contents)
2278 d.addCallback(_check2_mutable)
2282 self.failUnless(uri in FakeCHKFileNode.all_contents)
2283 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
2285 return self.GET("/uri/%s" % uri)
2286 d.addCallback(_check)
2288 self.failUnlessEqual(res, file_contents)
2289 d.addCallback(_check2)
2292 def test_PUT_mkdir(self):
2293 d = self.PUT("/uri?t=mkdir", "")
2295 n = self.s.create_node_from_uri(uri.strip())
2296 d2 = self.failUnlessNodeKeysAre(n, [])
2297 d2.addCallback(lambda res:
2298 self.GET("/uri/%s?t=json" % uri))
2300 d.addCallback(_check)
2301 d.addCallback(self.failUnlessIsEmptyJSON)
2304 def test_POST_check(self):
2305 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
2307 # this returns a string form of the results, which are probably
2308 # None since we're using fake filenodes.
2309 # TODO: verify that the check actually happened, by changing
2310 # FakeCHKFileNode to count how many times .check() has been
2313 d.addCallback(_done)
2316 def test_bad_method(self):
2317 url = self.webish_url + self.public_url + "/foo/bar.txt"
2318 d = self.shouldHTTPError2("test_bad_method",
2319 501, "Not Implemented",
2320 "I don't know how to treat a BOGUS request.",
2321 client.getPage, url, method="BOGUS")
2324 def test_short_url(self):
2325 url = self.webish_url + "/uri"
2326 d = self.shouldHTTPError2("test_short_url", 501, "Not Implemented",
2327 "I don't know how to treat a DELETE request.",
2328 client.getPage, url, method="DELETE")
2331 def test_ophandle_bad(self):
2332 url = self.webish_url + "/operations/bogus?t=status"
2333 d = self.shouldHTTPError2("test_ophandle_bad", 404, "404 Not Found",
2334 "unknown/expired handle 'bogus'",
2335 client.getPage, url)
2338 def test_ophandle_cancel(self):
2339 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
2340 followRedirect=True)
2341 d.addCallback(lambda ignored:
2342 self.GET("/operations/128?t=status&output=JSON"))
2344 data = simplejson.loads(res)
2345 self.failUnless("finished" in data, res)
2346 monitor = self.ws.root.child_operations.handles["128"][0]
2347 d = self.POST("/operations/128?t=cancel&output=JSON")
2349 data = simplejson.loads(res)
2350 self.failUnless("finished" in data, res)
2351 # t=cancel causes the handle to be forgotten
2352 self.failUnless(monitor.is_cancelled())
2353 d.addCallback(_check2)
2355 d.addCallback(_check1)
2356 d.addCallback(lambda ignored:
2357 self.shouldHTTPError2("test_ophandle_cancel",
2358 404, "404 Not Found",
2359 "unknown/expired handle '128'",
2361 "/operations/128?t=status&output=JSON"))
2364 def test_ophandle_retainfor(self):
2365 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
2366 followRedirect=True)
2367 d.addCallback(lambda ignored:
2368 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
2370 data = simplejson.loads(res)
2371 self.failUnless("finished" in data, res)
2372 d.addCallback(_check1)
2373 # the retain-for=0 will cause the handle to be expired very soon
2374 d.addCallback(self.stall, 2.0)
2375 d.addCallback(lambda ignored:
2376 self.shouldHTTPError2("test_ophandle_retainfor",
2377 404, "404 Not Found",
2378 "unknown/expired handle '129'",
2380 "/operations/129?t=status&output=JSON"))
2383 def test_ophandle_release_after_complete(self):
2384 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
2385 followRedirect=True)
2386 d.addCallback(self.wait_for_operation, "130")
2387 d.addCallback(lambda ignored:
2388 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
2389 # the release-after-complete=true will cause the handle to be expired
2390 d.addCallback(lambda ignored:
2391 self.shouldHTTPError2("test_ophandle_release_after_complete",
2392 404, "404 Not Found",
2393 "unknown/expired handle '130'",
2395 "/operations/130?t=status&output=JSON"))
2398 def test_incident(self):
2399 d = self.POST("/report_incident", details="eek")
2401 self.failUnless("Thank you for your report!" in res, res)
2402 d.addCallback(_done)
2405 def test_static(self):
2406 webdir = os.path.join(self.staticdir, "subdir")
2407 fileutil.make_dirs(webdir)
2408 f = open(os.path.join(webdir, "hello.txt"), "wb")
2412 d = self.GET("/static/subdir/hello.txt")
2414 self.failUnlessEqual(res, "hello")
2415 d.addCallback(_check)
2419 class Util(unittest.TestCase):
2420 def test_abbreviate_time(self):
2421 self.failUnlessEqual(common.abbreviate_time(None), "")
2422 self.failUnlessEqual(common.abbreviate_time(1.234), "1.23s")
2423 self.failUnlessEqual(common.abbreviate_time(0.123), "123ms")
2424 self.failUnlessEqual(common.abbreviate_time(0.00123), "1.2ms")
2425 self.failUnlessEqual(common.abbreviate_time(0.000123), "123us")
2427 def test_abbreviate_rate(self):
2428 self.failUnlessEqual(common.abbreviate_rate(None), "")
2429 self.failUnlessEqual(common.abbreviate_rate(1234000), "1.23MBps")
2430 self.failUnlessEqual(common.abbreviate_rate(12340), "12.3kBps")
2431 self.failUnlessEqual(common.abbreviate_rate(123), "123Bps")
2433 def test_abbreviate_size(self):
2434 self.failUnlessEqual(common.abbreviate_size(None), "")
2435 self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
2436 self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
2437 self.failUnlessEqual(common.abbreviate_size(1230), "1.2kB")
2438 self.failUnlessEqual(common.abbreviate_size(123), "123B")