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("stats" in res)
954 d.addCallback(_got_json)
957 def test_POST_DIRURL_deepsize_no_ophandle(self):
958 d = self.shouldFail2(error.Error,
959 "test_POST_DIRURL_deepsize_no_ophandle",
961 "slow operation requires ophandle=",
962 self.POST, self.public_url, t="start-deep-size")
965 def test_POST_DIRURL_deepsize(self):
966 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
968 d.addCallback(self.wait_for_operation, "126")
969 d.addCallback(self.get_operation_results, "126", "json")
971 self.failUnlessEqual(data["finished"], True)
973 self.failUnless(size > 1000)
974 d.addCallback(_got_json)
975 d.addCallback(self.get_operation_results, "126", "text")
977 mo = re.search(r'^size: (\d+)$', res, re.M)
978 self.failUnless(mo, res)
979 size = int(mo.group(1))
980 # with directories, the size varies.
981 self.failUnless(size > 1000)
982 d.addCallback(_got_text)
985 def test_POST_DIRURL_deepstats_no_ophandle(self):
986 d = self.shouldFail2(error.Error,
987 "test_POST_DIRURL_deepstats_no_ophandle",
989 "slow operation requires ophandle=",
990 self.POST, self.public_url, t="start-deep-stats")
993 def test_POST_DIRURL_deepstats(self):
994 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
996 d.addCallback(self.wait_for_operation, "127")
997 d.addCallback(self.get_operation_results, "127", "json")
998 def _got_json(stats):
999 expected = {"count-immutable-files": 3,
1000 "count-mutable-files": 0,
1001 "count-literal-files": 0,
1003 "count-directories": 3,
1004 "size-immutable-files": 57,
1005 "size-literal-files": 0,
1006 #"size-directories": 1912, # varies
1007 #"largest-directory": 1590,
1008 "largest-directory-children": 5,
1009 "largest-immutable-file": 19,
1011 for k,v in expected.iteritems():
1012 self.failUnlessEqual(stats[k], v,
1013 "stats[%s] was %s, not %s" %
1015 self.failUnlessEqual(stats["size-files-histogram"],
1017 d.addCallback(_got_json)
1020 def test_GET_DIRURL_uri(self):
1021 d = self.GET(self.public_url + "/foo?t=uri")
1023 self.failUnlessEqual(res, self._foo_uri)
1024 d.addCallback(_check)
1027 def test_GET_DIRURL_readonly_uri(self):
1028 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1030 self.failUnlessEqual(res, self._foo_readonly_uri)
1031 d.addCallback(_check)
1034 def test_PUT_NEWDIRURL(self):
1035 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1036 d.addCallback(lambda res:
1037 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1038 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1039 d.addCallback(self.failUnlessNodeKeysAre, [])
1042 def test_PUT_NEWDIRURL_exists(self):
1043 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1044 d.addCallback(lambda res:
1045 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1046 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1047 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1050 def test_PUT_NEWDIRURL_blocked(self):
1051 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
1052 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
1054 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
1055 d.addCallback(lambda res:
1056 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1057 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1058 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1061 def test_PUT_NEWDIRURL_mkdir_p(self):
1062 d = defer.succeed(None)
1063 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
1064 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
1065 d.addCallback(lambda res: self._foo_node.get(u"mkp"))
1066 def mkdir_p(mkpnode):
1067 url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
1069 def made_subsub(ssuri):
1070 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
1071 d.addCallback(lambda ssnode: self.failUnlessEqual(ssnode.get_uri(), ssuri))
1073 d.addCallback(lambda uri2: self.failUnlessEqual(uri2, ssuri))
1075 d.addCallback(made_subsub)
1077 d.addCallback(mkdir_p)
1080 def test_PUT_NEWDIRURL_mkdirs(self):
1081 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1082 d.addCallback(lambda res:
1083 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1084 d.addCallback(lambda res:
1085 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1086 d.addCallback(lambda res:
1087 self._foo_node.get_child_at_path(u"subdir/newdir"))
1088 d.addCallback(self.failUnlessNodeKeysAre, [])
1091 def test_DELETE_DIRURL(self):
1092 d = self.DELETE(self.public_url + "/foo")
1093 d.addCallback(lambda res:
1094 self.failIfNodeHasChild(self.public_root, u"foo"))
1097 def test_DELETE_DIRURL_missing(self):
1098 d = self.DELETE(self.public_url + "/foo/missing")
1099 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
1100 d.addCallback(lambda res:
1101 self.failUnlessNodeHasChild(self.public_root, u"foo"))
1104 def test_DELETE_DIRURL_missing2(self):
1105 d = self.DELETE(self.public_url + "/missing")
1106 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
1109 def dump_root(self):
1111 w = webish.DirnodeWalkerMixin()
1112 def visitor(childpath, childnode, metadata):
1114 d = w.walk(self.public_root, visitor)
1117 def failUnlessNodeKeysAre(self, node, expected_keys):
1118 for k in expected_keys:
1119 assert isinstance(k, unicode)
1121 def _check(children):
1122 self.failUnlessEqual(sorted(children.keys()), sorted(expected_keys))
1123 d.addCallback(_check)
1125 def failUnlessNodeHasChild(self, node, name):
1126 assert isinstance(name, unicode)
1128 def _check(children):
1129 self.failUnless(name in children)
1130 d.addCallback(_check)
1132 def failIfNodeHasChild(self, node, name):
1133 assert isinstance(name, unicode)
1135 def _check(children):
1136 self.failIf(name in children)
1137 d.addCallback(_check)
1140 def failUnlessChildContentsAre(self, node, name, expected_contents):
1141 assert isinstance(name, unicode)
1142 d = node.get_child_at_path(name)
1143 d.addCallback(lambda node: node.download_to_data())
1144 def _check(contents):
1145 self.failUnlessEqual(contents, expected_contents)
1146 d.addCallback(_check)
1149 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
1150 assert isinstance(name, unicode)
1151 d = node.get_child_at_path(name)
1152 d.addCallback(lambda node: node.download_best_version())
1153 def _check(contents):
1154 self.failUnlessEqual(contents, expected_contents)
1155 d.addCallback(_check)
1158 def failUnlessChildURIIs(self, node, name, expected_uri):
1159 assert isinstance(name, unicode)
1160 d = node.get_child_at_path(name)
1162 self.failUnlessEqual(child.get_uri(), expected_uri.strip())
1163 d.addCallback(_check)
1166 def failUnlessURIMatchesChild(self, got_uri, node, name):
1167 assert isinstance(name, unicode)
1168 d = node.get_child_at_path(name)
1170 self.failUnlessEqual(got_uri.strip(), child.get_uri())
1171 d.addCallback(_check)
1174 def failUnlessCHKURIHasContents(self, got_uri, contents):
1175 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
1177 def test_POST_upload(self):
1178 d = self.POST(self.public_url + "/foo", t="upload",
1179 file=("new.txt", self.NEWFILE_CONTENTS))
1181 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1182 d.addCallback(lambda res:
1183 self.failUnlessChildContentsAre(fn, u"new.txt",
1184 self.NEWFILE_CONTENTS))
1187 def test_POST_upload_unicode(self):
1188 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1189 d = self.POST(self.public_url + "/foo", t="upload",
1190 file=(filename, self.NEWFILE_CONTENTS))
1192 d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1193 d.addCallback(lambda res:
1194 self.failUnlessChildContentsAre(fn, filename,
1195 self.NEWFILE_CONTENTS))
1196 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1197 d.addCallback(lambda res: self.GET(target_url))
1198 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1199 self.NEWFILE_CONTENTS,
1203 def test_POST_upload_unicode_named(self):
1204 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1205 d = self.POST(self.public_url + "/foo", t="upload",
1207 file=("overridden", self.NEWFILE_CONTENTS))
1209 d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1210 d.addCallback(lambda res:
1211 self.failUnlessChildContentsAre(fn, filename,
1212 self.NEWFILE_CONTENTS))
1213 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1214 d.addCallback(lambda res: self.GET(target_url))
1215 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1216 self.NEWFILE_CONTENTS,
1220 def test_POST_upload_no_link(self):
1221 d = self.POST("/uri", t="upload",
1222 file=("new.txt", self.NEWFILE_CONTENTS))
1223 def _check_upload_results(page):
1224 # this should be a page which describes the results of the upload
1225 # that just finished.
1226 self.failUnless("Upload Results:" in page)
1227 self.failUnless("URI:" in page)
1228 uri_re = re.compile("URI: <tt><span>(.*)</span>")
1229 mo = uri_re.search(page)
1230 self.failUnless(mo, page)
1231 new_uri = mo.group(1)
1233 d.addCallback(_check_upload_results)
1234 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1237 def test_POST_upload_no_link_whendone(self):
1238 d = self.POST("/uri", t="upload", when_done="/",
1239 file=("new.txt", self.NEWFILE_CONTENTS))
1240 d.addBoth(self.shouldRedirect, "/")
1243 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
1244 d = defer.maybeDeferred(callable, *args, **kwargs)
1246 if isinstance(res, failure.Failure):
1247 res.trap(error.PageRedirect)
1248 statuscode = res.value.status
1249 target = res.value.location
1250 return checker(statuscode, target)
1251 self.fail("%s: callable was supposed to redirect, not return '%s'"
1256 def test_POST_upload_no_link_whendone_results(self):
1257 def check(statuscode, target):
1258 self.failUnlessEqual(statuscode, str(http.FOUND))
1259 self.failUnless(target.startswith(self.webish_url), target)
1260 return client.getPage(target, method="GET")
1261 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
1263 self.POST, "/uri", t="upload",
1264 when_done="/uri/%(uri)s",
1265 file=("new.txt", self.NEWFILE_CONTENTS))
1266 d.addCallback(lambda res:
1267 self.failUnlessEqual(res, self.NEWFILE_CONTENTS))
1270 def test_POST_upload_no_link_mutable(self):
1271 d = self.POST("/uri", t="upload", mutable="true",
1272 file=("new.txt", self.NEWFILE_CONTENTS))
1273 def _check(new_uri):
1274 new_uri = new_uri.strip()
1275 self.new_uri = new_uri
1277 self.failUnless(IMutableFileURI.providedBy(u))
1278 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
1279 n = self.s.create_node_from_uri(new_uri)
1280 return n.download_best_version()
1281 d.addCallback(_check)
1283 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1284 return self.GET("/uri/%s" % urllib.quote(self.new_uri))
1285 d.addCallback(_check2)
1287 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1288 return self.GET("/file/%s" % urllib.quote(self.new_uri))
1289 d.addCallback(_check3)
1291 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1292 d.addCallback(_check4)
1295 def test_POST_upload_no_link_mutable_toobig(self):
1296 d = self.shouldFail2(error.Error,
1297 "test_POST_upload_no_link_mutable_toobig",
1298 "413 Request Entity Too Large",
1299 "SDMF is limited to one segment, and 10001 > 10000",
1301 "/uri", t="upload", mutable="true",
1303 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1306 def test_POST_upload_mutable(self):
1307 # this creates a mutable file
1308 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
1309 file=("new.txt", self.NEWFILE_CONTENTS))
1311 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1312 d.addCallback(lambda res:
1313 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1314 self.NEWFILE_CONTENTS))
1315 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1317 self.failUnless(IMutableFileNode.providedBy(newnode))
1318 self.failUnless(newnode.is_mutable())
1319 self.failIf(newnode.is_readonly())
1320 self._mutable_node = newnode
1321 self._mutable_uri = newnode.get_uri()
1324 # now upload it again and make sure that the URI doesn't change
1325 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
1326 d.addCallback(lambda res:
1327 self.POST(self.public_url + "/foo", t="upload",
1329 file=("new.txt", NEWER_CONTENTS)))
1330 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1331 d.addCallback(lambda res:
1332 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1334 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1336 self.failUnless(IMutableFileNode.providedBy(newnode))
1337 self.failUnless(newnode.is_mutable())
1338 self.failIf(newnode.is_readonly())
1339 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1340 d.addCallback(_got2)
1342 # upload a second time, using PUT instead of POST
1343 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
1344 d.addCallback(lambda res:
1345 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
1346 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1347 d.addCallback(lambda res:
1348 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1351 # finally list the directory, since mutable files are displayed
1352 # slightly differently
1354 d.addCallback(lambda res:
1355 self.GET(self.public_url + "/foo/",
1356 followRedirect=True))
1357 def _check_page(res):
1358 # TODO: assert more about the contents
1359 self.failUnless("SSK" in res)
1361 d.addCallback(_check_page)
1363 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1365 self.failUnless(IMutableFileNode.providedBy(newnode))
1366 self.failUnless(newnode.is_mutable())
1367 self.failIf(newnode.is_readonly())
1368 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1369 d.addCallback(_got3)
1371 # look at the JSON form of the enclosing directory
1372 d.addCallback(lambda res:
1373 self.GET(self.public_url + "/foo/?t=json",
1374 followRedirect=True))
1375 def _check_page_json(res):
1376 parsed = simplejson.loads(res)
1377 self.failUnlessEqual(parsed[0], "dirnode")
1378 children = dict( [(unicode(name),value)
1380 in parsed[1]["children"].iteritems()] )
1381 self.failUnless("new.txt" in children)
1382 new_json = children["new.txt"]
1383 self.failUnlessEqual(new_json[0], "filenode")
1384 self.failUnless(new_json[1]["mutable"])
1385 self.failUnlessEqual(new_json[1]["rw_uri"], self._mutable_uri)
1386 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1387 self.failUnlessEqual(new_json[1]["ro_uri"], ro_uri)
1388 d.addCallback(_check_page_json)
1390 # and the JSON form of the file
1391 d.addCallback(lambda res:
1392 self.GET(self.public_url + "/foo/new.txt?t=json"))
1393 def _check_file_json(res):
1394 parsed = simplejson.loads(res)
1395 self.failUnlessEqual(parsed[0], "filenode")
1396 self.failUnless(parsed[1]["mutable"])
1397 self.failUnlessEqual(parsed[1]["rw_uri"], self._mutable_uri)
1398 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1399 self.failUnlessEqual(parsed[1]["ro_uri"], ro_uri)
1400 d.addCallback(_check_file_json)
1402 # and look at t=uri and t=readonly-uri
1403 d.addCallback(lambda res:
1404 self.GET(self.public_url + "/foo/new.txt?t=uri"))
1405 d.addCallback(lambda res: self.failUnlessEqual(res, self._mutable_uri))
1406 d.addCallback(lambda res:
1407 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
1408 def _check_ro_uri(res):
1409 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1410 self.failUnlessEqual(res, ro_uri)
1411 d.addCallback(_check_ro_uri)
1413 # make sure we can get to it from /uri/URI
1414 d.addCallback(lambda res:
1415 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
1416 d.addCallback(lambda res:
1417 self.failUnlessEqual(res, NEW2_CONTENTS))
1419 # and that HEAD computes the size correctly
1420 d.addCallback(lambda res:
1421 self.HEAD(self.public_url + "/foo/new.txt",
1422 return_response=True))
1423 def _got_headers((res, status, headers)):
1424 self.failUnlessEqual(res, "")
1425 self.failUnlessEqual(headers["content-length"][0],
1426 str(len(NEW2_CONTENTS)))
1427 self.failUnlessEqual(headers["content-type"], ["text/plain"])
1428 d.addCallback(_got_headers)
1430 # make sure that size errors are displayed correctly for overwrite
1431 d.addCallback(lambda res:
1432 self.shouldFail2(error.Error,
1433 "test_POST_upload_mutable-toobig",
1434 "413 Request Entity Too Large",
1435 "SDMF is limited to one segment, and 10001 > 10000",
1437 self.public_url + "/foo", t="upload",
1440 "b" * (self.s.MUTABLE_SIZELIMIT+1)),
1443 d.addErrback(self.dump_error)
1446 def test_POST_upload_mutable_toobig(self):
1447 d = self.shouldFail2(error.Error,
1448 "test_POST_upload_no_link_mutable_toobig",
1449 "413 Request Entity Too Large",
1450 "SDMF is limited to one segment, and 10001 > 10000",
1452 self.public_url + "/foo",
1453 t="upload", mutable="true",
1455 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1458 def dump_error(self, f):
1459 # if the web server returns an error code (like 400 Bad Request),
1460 # web.client.getPage puts the HTTP response body into the .response
1461 # attribute of the exception object that it gives back. It does not
1462 # appear in the Failure's repr(), so the ERROR that trial displays
1463 # will be rather terse and unhelpful. addErrback this method to the
1464 # end of your chain to get more information out of these errors.
1465 if f.check(error.Error):
1466 print "web.error.Error:"
1468 print f.value.response
1471 def test_POST_upload_replace(self):
1472 d = self.POST(self.public_url + "/foo", t="upload",
1473 file=("bar.txt", self.NEWFILE_CONTENTS))
1475 d.addCallback(self.failUnlessURIMatchesChild, fn, u"bar.txt")
1476 d.addCallback(lambda res:
1477 self.failUnlessChildContentsAre(fn, u"bar.txt",
1478 self.NEWFILE_CONTENTS))
1481 def test_POST_upload_no_replace_ok(self):
1482 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1483 file=("new.txt", self.NEWFILE_CONTENTS))
1484 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
1485 d.addCallback(lambda res: self.failUnlessEqual(res,
1486 self.NEWFILE_CONTENTS))
1489 def test_POST_upload_no_replace_queryarg(self):
1490 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1491 file=("bar.txt", self.NEWFILE_CONTENTS))
1492 d.addBoth(self.shouldFail, error.Error,
1493 "POST_upload_no_replace_queryarg",
1495 "There was already a child by that name, and you asked me "
1496 "to not replace it")
1497 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1498 d.addCallback(self.failUnlessIsBarDotTxt)
1501 def test_POST_upload_no_replace_field(self):
1502 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
1503 file=("bar.txt", self.NEWFILE_CONTENTS))
1504 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
1506 "There was already a child by that name, and you asked me "
1507 "to not replace it")
1508 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1509 d.addCallback(self.failUnlessIsBarDotTxt)
1512 def test_POST_upload_whendone(self):
1513 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
1514 file=("new.txt", self.NEWFILE_CONTENTS))
1515 d.addBoth(self.shouldRedirect, "/THERE")
1517 d.addCallback(lambda res:
1518 self.failUnlessChildContentsAre(fn, u"new.txt",
1519 self.NEWFILE_CONTENTS))
1522 def test_POST_upload_named(self):
1524 d = self.POST(self.public_url + "/foo", t="upload",
1525 name="new.txt", file=self.NEWFILE_CONTENTS)
1526 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1527 d.addCallback(lambda res:
1528 self.failUnlessChildContentsAre(fn, u"new.txt",
1529 self.NEWFILE_CONTENTS))
1532 def test_POST_upload_named_badfilename(self):
1533 d = self.POST(self.public_url + "/foo", t="upload",
1534 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
1535 d.addBoth(self.shouldFail, error.Error,
1536 "test_POST_upload_named_badfilename",
1538 "name= may not contain a slash",
1540 # make sure that nothing was added
1541 d.addCallback(lambda res:
1542 self.failUnlessNodeKeysAre(self._foo_node,
1543 [u"bar.txt", u"blockingfile",
1544 u"empty", u"n\u00fc.txt",
1548 def test_POST_FILEURL_check(self):
1549 bar_url = self.public_url + "/foo/bar.txt"
1550 d = self.POST(bar_url, t="check")
1552 self.failUnless("Healthy :" in res)
1553 d.addCallback(_check)
1554 redir_url = "http://allmydata.org/TARGET"
1555 def _check2(statuscode, target):
1556 self.failUnlessEqual(statuscode, str(http.FOUND))
1557 self.failUnlessEqual(target, redir_url)
1558 d.addCallback(lambda res:
1559 self.shouldRedirect2("test_POST_FILEURL_check",
1563 when_done=redir_url))
1564 d.addCallback(lambda res:
1565 self.POST(bar_url, t="check", return_to=redir_url))
1567 self.failUnless("Healthy :" in res)
1568 self.failUnless("Return to parent directory" in res)
1569 self.failUnless(redir_url in res)
1570 d.addCallback(_check3)
1572 d.addCallback(lambda res:
1573 self.POST(bar_url, t="check", output="JSON"))
1574 def _check_json(res):
1575 data = simplejson.loads(res)
1576 self.failUnless("storage-index" in data)
1577 self.failUnless(data["results"]["healthy"])
1578 d.addCallback(_check_json)
1582 def test_POST_FILEURL_check_and_repair(self):
1583 bar_url = self.public_url + "/foo/bar.txt"
1584 d = self.POST(bar_url, t="check", repair="true")
1586 self.failUnless("Healthy :" in res)
1587 d.addCallback(_check)
1588 redir_url = "http://allmydata.org/TARGET"
1589 def _check2(statuscode, target):
1590 self.failUnlessEqual(statuscode, str(http.FOUND))
1591 self.failUnlessEqual(target, redir_url)
1592 d.addCallback(lambda res:
1593 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
1596 t="check", repair="true",
1597 when_done=redir_url))
1598 d.addCallback(lambda res:
1599 self.POST(bar_url, t="check", return_to=redir_url))
1601 self.failUnless("Healthy :" in res)
1602 self.failUnless("Return to parent directory" in res)
1603 self.failUnless(redir_url in res)
1604 d.addCallback(_check3)
1607 def test_POST_DIRURL_check(self):
1608 foo_url = self.public_url + "/foo/"
1609 d = self.POST(foo_url, t="check")
1611 self.failUnless("Healthy :" in res, res)
1612 d.addCallback(_check)
1613 redir_url = "http://allmydata.org/TARGET"
1614 def _check2(statuscode, target):
1615 self.failUnlessEqual(statuscode, str(http.FOUND))
1616 self.failUnlessEqual(target, redir_url)
1617 d.addCallback(lambda res:
1618 self.shouldRedirect2("test_POST_DIRURL_check",
1622 when_done=redir_url))
1623 d.addCallback(lambda res:
1624 self.POST(foo_url, t="check", return_to=redir_url))
1626 self.failUnless("Healthy :" in res, res)
1627 self.failUnless("Return to parent directory" in res)
1628 self.failUnless(redir_url in res)
1629 d.addCallback(_check3)
1631 d.addCallback(lambda res:
1632 self.POST(foo_url, t="check", output="JSON"))
1633 def _check_json(res):
1634 data = simplejson.loads(res)
1635 self.failUnless("storage-index" in data)
1636 self.failUnless(data["results"]["healthy"])
1637 d.addCallback(_check_json)
1641 def test_POST_DIRURL_check_and_repair(self):
1642 foo_url = self.public_url + "/foo/"
1643 d = self.POST(foo_url, t="check", repair="true")
1645 self.failUnless("Healthy :" in res, res)
1646 d.addCallback(_check)
1647 redir_url = "http://allmydata.org/TARGET"
1648 def _check2(statuscode, target):
1649 self.failUnlessEqual(statuscode, str(http.FOUND))
1650 self.failUnlessEqual(target, redir_url)
1651 d.addCallback(lambda res:
1652 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
1655 t="check", repair="true",
1656 when_done=redir_url))
1657 d.addCallback(lambda res:
1658 self.POST(foo_url, t="check", return_to=redir_url))
1660 self.failUnless("Healthy :" in res)
1661 self.failUnless("Return to parent directory" in res)
1662 self.failUnless(redir_url in res)
1663 d.addCallback(_check3)
1666 def wait_for_operation(self, ignored, ophandle):
1667 url = "/operations/" + ophandle
1668 url += "?t=status&output=JSON"
1671 data = simplejson.loads(res)
1672 if not data["finished"]:
1673 d = self.stall(delay=1.0)
1674 d.addCallback(self.wait_for_operation, ophandle)
1680 def get_operation_results(self, ignored, ophandle, output=None):
1681 url = "/operations/" + ophandle
1684 url += "&output=" + output
1687 if output and output.lower() == "json":
1688 return simplejson.loads(res)
1693 def test_POST_DIRURL_deepcheck_no_ophandle(self):
1694 d = self.shouldFail2(error.Error,
1695 "test_POST_DIRURL_deepcheck_no_ophandle",
1697 "slow operation requires ophandle=",
1698 self.POST, self.public_url, t="start-deep-check")
1701 def test_POST_DIRURL_deepcheck(self):
1702 def _check_redirect(statuscode, target):
1703 self.failUnlessEqual(statuscode, str(http.FOUND))
1704 self.failUnless(target.endswith("/operations/123"))
1705 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
1706 self.POST, self.public_url,
1707 t="start-deep-check", ophandle="123")
1708 d.addCallback(self.wait_for_operation, "123")
1709 def _check_json(data):
1710 self.failUnlessEqual(data["finished"], True)
1711 self.failUnlessEqual(data["count-objects-checked"], 8)
1712 self.failUnlessEqual(data["count-objects-healthy"], 8)
1713 d.addCallback(_check_json)
1714 d.addCallback(self.get_operation_results, "123", "html")
1715 def _check_html(res):
1716 self.failUnless("Objects Checked: <span>8</span>" in res)
1717 self.failUnless("Objects Healthy: <span>8</span>" in res)
1718 d.addCallback(_check_html)
1720 d.addCallback(lambda res:
1721 self.GET("/operations/123/"))
1722 d.addCallback(_check_html) # should be the same as without the slash
1724 d.addCallback(lambda res:
1725 self.shouldFail2(error.Error, "one", "404 Not Found",
1726 "No detailed results for SI bogus",
1727 self.GET, "/operations/123/bogus"))
1729 foo_si = self._foo_node.get_storage_index()
1730 foo_si_s = base32.b2a(foo_si)
1731 d.addCallback(lambda res:
1732 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
1733 def _check_foo_json(res):
1734 data = simplejson.loads(res)
1735 self.failUnlessEqual(data["storage-index"], foo_si_s)
1736 self.failUnless(data["results"]["healthy"])
1737 d.addCallback(_check_foo_json)
1740 def test_POST_DIRURL_deepcheck_and_repair(self):
1741 d = self.POST(self.public_url, t="start-deep-check", repair="true",
1742 ophandle="124", output="json", followRedirect=True)
1743 d.addCallback(self.wait_for_operation, "124")
1744 def _check_json(data):
1745 self.failUnlessEqual(data["finished"], True)
1746 self.failUnlessEqual(data["count-objects-checked"], 8)
1747 self.failUnlessEqual(data["count-objects-healthy-pre-repair"], 8)
1748 self.failUnlessEqual(data["count-objects-unhealthy-pre-repair"], 0)
1749 self.failUnlessEqual(data["count-corrupt-shares-pre-repair"], 0)
1750 self.failUnlessEqual(data["count-repairs-attempted"], 0)
1751 self.failUnlessEqual(data["count-repairs-successful"], 0)
1752 self.failUnlessEqual(data["count-repairs-unsuccessful"], 0)
1753 self.failUnlessEqual(data["count-objects-healthy-post-repair"], 8)
1754 self.failUnlessEqual(data["count-objects-unhealthy-post-repair"], 0)
1755 self.failUnlessEqual(data["count-corrupt-shares-post-repair"], 0)
1756 d.addCallback(_check_json)
1757 d.addCallback(self.get_operation_results, "124", "html")
1758 def _check_html(res):
1759 self.failUnless("Objects Checked: <span>8</span>" in res)
1761 self.failUnless("Objects Healthy (before repair): <span>8</span>" in res)
1762 self.failUnless("Objects Unhealthy (before repair): <span>0</span>" in res)
1763 self.failUnless("Corrupt Shares (before repair): <span>0</span>" in res)
1765 self.failUnless("Repairs Attempted: <span>0</span>" in res)
1766 self.failUnless("Repairs Successful: <span>0</span>" in res)
1767 self.failUnless("Repairs Unsuccessful: <span>0</span>" in res)
1769 self.failUnless("Objects Healthy (after repair): <span>8</span>" in res)
1770 self.failUnless("Objects Unhealthy (after repair): <span>0</span>" in res)
1771 self.failUnless("Corrupt Shares (after repair): <span>0</span>" in res)
1772 d.addCallback(_check_html)
1775 def test_POST_FILEURL_bad_t(self):
1776 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
1777 "POST to file: bad t=bogus",
1778 self.POST, self.public_url + "/foo/bar.txt",
1782 def test_POST_mkdir(self): # return value?
1783 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
1784 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1785 d.addCallback(self.failUnlessNodeKeysAre, [])
1788 def test_POST_mkdir_2(self):
1789 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
1790 d.addCallback(lambda res:
1791 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1792 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1793 d.addCallback(self.failUnlessNodeKeysAre, [])
1796 def test_POST_mkdirs_2(self):
1797 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
1798 d.addCallback(lambda res:
1799 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
1800 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
1801 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
1802 d.addCallback(self.failUnlessNodeKeysAre, [])
1805 def test_POST_mkdir_no_parentdir_noredirect(self):
1806 d = self.POST("/uri?t=mkdir")
1807 def _after_mkdir(res):
1808 uri.NewDirectoryURI.init_from_string(res)
1809 d.addCallback(_after_mkdir)
1812 def test_POST_mkdir_no_parentdir_redirect(self):
1813 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
1814 d.addBoth(self.shouldRedirect, None, statuscode='303')
1815 def _check_target(target):
1816 target = urllib.unquote(target)
1817 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
1818 d.addCallback(_check_target)
1821 def test_POST_noparent_bad(self):
1822 d = self.shouldHTTPError2("POST /uri?t=bogus", 400, "Bad Request",
1823 "/uri accepts only PUT, PUT?t=mkdir, "
1824 "POST?t=upload, and POST?t=mkdir",
1825 self.POST, "/uri?t=bogus")
1828 def test_welcome_page_mkdir_button(self):
1829 # Fetch the welcome page.
1831 def _after_get_welcome_page(res):
1832 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)
1833 mo = MKDIR_BUTTON_RE.search(res)
1834 formaction = mo.group(1)
1836 formaname = mo.group(3)
1837 formavalue = mo.group(4)
1838 return (formaction, formt, formaname, formavalue)
1839 d.addCallback(_after_get_welcome_page)
1840 def _after_parse_form(res):
1841 (formaction, formt, formaname, formavalue) = res
1842 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
1843 d.addCallback(_after_parse_form)
1844 d.addBoth(self.shouldRedirect, None, statuscode='303')
1847 def test_POST_mkdir_replace(self): # return value?
1848 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
1849 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1850 d.addCallback(self.failUnlessNodeKeysAre, [])
1853 def test_POST_mkdir_no_replace_queryarg(self): # return value?
1854 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
1855 d.addBoth(self.shouldFail, error.Error,
1856 "POST_mkdir_no_replace_queryarg",
1858 "There was already a child by that name, and you asked me "
1859 "to not replace it")
1860 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1861 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1864 def test_POST_mkdir_no_replace_field(self): # return value?
1865 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
1867 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
1869 "There was already a child by that name, and you asked me "
1870 "to not replace it")
1871 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1872 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1875 def test_POST_mkdir_whendone_field(self):
1876 d = self.POST(self.public_url + "/foo",
1877 t="mkdir", name="newdir", when_done="/THERE")
1878 d.addBoth(self.shouldRedirect, "/THERE")
1879 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1880 d.addCallback(self.failUnlessNodeKeysAre, [])
1883 def test_POST_mkdir_whendone_queryarg(self):
1884 d = self.POST(self.public_url + "/foo?when_done=/THERE",
1885 t="mkdir", name="newdir")
1886 d.addBoth(self.shouldRedirect, "/THERE")
1887 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1888 d.addCallback(self.failUnlessNodeKeysAre, [])
1891 def test_POST_bad_t(self):
1892 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
1893 "POST to a directory with bad t=BOGUS",
1894 self.POST, self.public_url + "/foo", t="BOGUS")
1897 def test_POST_set_children(self):
1898 contents9, n9, newuri9 = self.makefile(9)
1899 contents10, n10, newuri10 = self.makefile(10)
1900 contents11, n11, newuri11 = self.makefile(11)
1903 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
1906 "ctime": 1002777696.7564139,
1907 "mtime": 1002777696.7564139
1910 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
1913 "ctime": 1002777696.7564139,
1914 "mtime": 1002777696.7564139
1917 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
1920 "ctime": 1002777696.7564139,
1921 "mtime": 1002777696.7564139
1924 }""" % (newuri9, newuri10, newuri11)
1926 url = self.webish_url + self.public_url + "/foo" + "?t=set_children"
1928 d = client.getPage(url, method="POST", postdata=reqbody)
1930 self.failUnlessURIMatchesChild(newuri9, self._foo_node, u"atomic_added_1")
1931 self.failUnlessURIMatchesChild(newuri10, self._foo_node, u"atomic_added_2")
1932 self.failUnlessURIMatchesChild(newuri11, self._foo_node, u"atomic_added_3")
1934 d.addCallback(_then)
1935 d.addErrback(self.dump_error)
1938 def test_POST_put_uri(self):
1939 contents, n, newuri = self.makefile(8)
1940 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
1941 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
1942 d.addCallback(lambda res:
1943 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1947 def test_POST_put_uri_replace(self):
1948 contents, n, newuri = self.makefile(8)
1949 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
1950 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
1951 d.addCallback(lambda res:
1952 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1956 def test_POST_put_uri_no_replace_queryarg(self):
1957 contents, n, newuri = self.makefile(8)
1958 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
1959 name="bar.txt", uri=newuri)
1960 d.addBoth(self.shouldFail, error.Error,
1961 "POST_put_uri_no_replace_queryarg",
1963 "There was already a child by that name, and you asked me "
1964 "to not replace it")
1965 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1966 d.addCallback(self.failUnlessIsBarDotTxt)
1969 def test_POST_put_uri_no_replace_field(self):
1970 contents, n, newuri = self.makefile(8)
1971 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
1972 name="bar.txt", uri=newuri)
1973 d.addBoth(self.shouldFail, error.Error,
1974 "POST_put_uri_no_replace_field",
1976 "There was already a child by that name, and you asked me "
1977 "to not replace it")
1978 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1979 d.addCallback(self.failUnlessIsBarDotTxt)
1982 def test_POST_delete(self):
1983 d = self.POST(self.public_url + "/foo", t="delete", name="bar.txt")
1984 d.addCallback(lambda res: self._foo_node.list())
1985 def _check(children):
1986 self.failIf(u"bar.txt" in children)
1987 d.addCallback(_check)
1990 def test_POST_rename_file(self):
1991 d = self.POST(self.public_url + "/foo", t="rename",
1992 from_name="bar.txt", to_name='wibble.txt')
1993 d.addCallback(lambda res:
1994 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1995 d.addCallback(lambda res:
1996 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
1997 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
1998 d.addCallback(self.failUnlessIsBarDotTxt)
1999 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
2000 d.addCallback(self.failUnlessIsBarJSON)
2003 def test_POST_rename_file_redundant(self):
2004 d = self.POST(self.public_url + "/foo", t="rename",
2005 from_name="bar.txt", to_name='bar.txt')
2006 d.addCallback(lambda res:
2007 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2008 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2009 d.addCallback(self.failUnlessIsBarDotTxt)
2010 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
2011 d.addCallback(self.failUnlessIsBarJSON)
2014 def test_POST_rename_file_replace(self):
2015 # rename a file and replace a directory with it
2016 d = self.POST(self.public_url + "/foo", t="rename",
2017 from_name="bar.txt", to_name='empty')
2018 d.addCallback(lambda res:
2019 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
2020 d.addCallback(lambda res:
2021 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
2022 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
2023 d.addCallback(self.failUnlessIsBarDotTxt)
2024 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2025 d.addCallback(self.failUnlessIsBarJSON)
2028 def test_POST_rename_file_no_replace_queryarg(self):
2029 # rename a file and replace a directory with it
2030 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
2031 from_name="bar.txt", to_name='empty')
2032 d.addBoth(self.shouldFail, error.Error,
2033 "POST_rename_file_no_replace_queryarg",
2035 "There was already a child by that name, and you asked me "
2036 "to not replace it")
2037 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2038 d.addCallback(self.failUnlessIsEmptyJSON)
2041 def test_POST_rename_file_no_replace_field(self):
2042 # rename a file and replace a directory with it
2043 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
2044 from_name="bar.txt", to_name='empty')
2045 d.addBoth(self.shouldFail, error.Error,
2046 "POST_rename_file_no_replace_field",
2048 "There was already a child by that name, and you asked me "
2049 "to not replace it")
2050 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2051 d.addCallback(self.failUnlessIsEmptyJSON)
2054 def failUnlessIsEmptyJSON(self, res):
2055 data = simplejson.loads(res)
2056 self.failUnlessEqual(data[0], "dirnode", data)
2057 self.failUnlessEqual(len(data[1]["children"]), 0)
2059 def test_POST_rename_file_slash_fail(self):
2060 d = self.POST(self.public_url + "/foo", t="rename",
2061 from_name="bar.txt", to_name='kirk/spock.txt')
2062 d.addBoth(self.shouldFail, error.Error,
2063 "test_POST_rename_file_slash_fail",
2065 "to_name= may not contain a slash",
2067 d.addCallback(lambda res:
2068 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2071 def test_POST_rename_dir(self):
2072 d = self.POST(self.public_url, t="rename",
2073 from_name="foo", to_name='plunk')
2074 d.addCallback(lambda res:
2075 self.failIfNodeHasChild(self.public_root, u"foo"))
2076 d.addCallback(lambda res:
2077 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
2078 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
2079 d.addCallback(self.failUnlessIsFooJSON)
2082 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
2083 """ If target is not None then the redirection has to go to target. If
2084 statuscode is not None then the redirection has to be accomplished with
2085 that HTTP status code."""
2086 if not isinstance(res, failure.Failure):
2087 to_where = (target is None) and "somewhere" or ("to " + target)
2088 self.fail("%s: we were expecting to get redirected %s, not get an"
2089 " actual page: %s" % (which, to_where, res))
2090 res.trap(error.PageRedirect)
2091 if statuscode is not None:
2092 self.failUnlessEqual(res.value.status, statuscode,
2093 "%s: not a redirect" % which)
2094 if target is not None:
2095 # the PageRedirect does not seem to capture the uri= query arg
2096 # properly, so we can't check for it.
2097 realtarget = self.webish_url + target
2098 self.failUnlessEqual(res.value.location, realtarget,
2099 "%s: wrong target" % which)
2100 return res.value.location
2102 def test_GET_URI_form(self):
2103 base = "/uri?uri=%s" % self._bar_txt_uri
2104 # this is supposed to give us a redirect to /uri/$URI, plus arguments
2105 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
2107 d.addBoth(self.shouldRedirect, targetbase)
2108 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
2109 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
2110 d.addCallback(lambda res: self.GET(base+"&t=json"))
2111 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
2112 d.addCallback(self.log, "about to get file by uri")
2113 d.addCallback(lambda res: self.GET(base, followRedirect=True))
2114 d.addCallback(self.failUnlessIsBarDotTxt)
2115 d.addCallback(self.log, "got file by uri, about to get dir by uri")
2116 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
2117 followRedirect=True))
2118 d.addCallback(self.failUnlessIsFooJSON)
2119 d.addCallback(self.log, "got dir by uri")
2123 def test_GET_URI_form_bad(self):
2124 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
2125 "400 Bad Request", "GET /uri requires uri=",
2129 def test_GET_rename_form(self):
2130 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
2131 followRedirect=True)
2133 self.failUnless('name="when_done" value="."' in res, res)
2134 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
2135 d.addCallback(_check)
2138 def log(self, res, msg):
2139 #print "MSG: %s RES: %s" % (msg, res)
2143 def test_GET_URI_URL(self):
2144 base = "/uri/%s" % self._bar_txt_uri
2146 d.addCallback(self.failUnlessIsBarDotTxt)
2147 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
2148 d.addCallback(self.failUnlessIsBarDotTxt)
2149 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
2150 d.addCallback(self.failUnlessIsBarDotTxt)
2153 def test_GET_URI_URL_dir(self):
2154 base = "/uri/%s?t=json" % self._foo_uri
2156 d.addCallback(self.failUnlessIsFooJSON)
2159 def test_GET_URI_URL_missing(self):
2160 base = "/uri/%s" % self._bad_file_uri
2162 d.addBoth(self.shouldHTTPError, "test_GET_URI_URL_missing",
2163 http.GONE, response_substring="NotEnoughSharesError")
2164 # TODO: how can we exercise both sides of WebDownloadTarget.fail
2165 # here? we must arrange for a download to fail after target.open()
2166 # has been called, and then inspect the response to see that it is
2167 # shorter than we expected.
2170 def test_PUT_DIRURL_uri(self):
2171 d = self.s.create_empty_dirnode()
2173 new_uri = dn.get_uri()
2174 # replace /foo with a new (empty) directory
2175 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
2176 d.addCallback(lambda res:
2177 self.failUnlessEqual(res.strip(), new_uri))
2178 d.addCallback(lambda res:
2179 self.failUnlessChildURIIs(self.public_root,
2183 d.addCallback(_made_dir)
2186 def test_PUT_DIRURL_uri_noreplace(self):
2187 d = self.s.create_empty_dirnode()
2189 new_uri = dn.get_uri()
2190 # replace /foo with a new (empty) directory, but ask that
2191 # replace=false, so it should fail
2192 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
2193 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
2195 self.public_url + "/foo?t=uri&replace=false",
2197 d.addCallback(lambda res:
2198 self.failUnlessChildURIIs(self.public_root,
2202 d.addCallback(_made_dir)
2205 def test_PUT_DIRURL_bad_t(self):
2206 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
2207 "400 Bad Request", "PUT to a directory",
2208 self.PUT, self.public_url + "/foo?t=BOGUS", "")
2209 d.addCallback(lambda res:
2210 self.failUnlessChildURIIs(self.public_root,
2215 def test_PUT_NEWFILEURL_uri(self):
2216 contents, n, new_uri = self.makefile(8)
2217 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
2218 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2219 d.addCallback(lambda res:
2220 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
2224 def test_PUT_NEWFILEURL_uri_replace(self):
2225 contents, n, new_uri = self.makefile(8)
2226 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
2227 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2228 d.addCallback(lambda res:
2229 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
2233 def test_PUT_NEWFILEURL_uri_no_replace(self):
2234 contents, n, new_uri = self.makefile(8)
2235 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
2236 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_uri_no_replace",
2238 "There was already a child by that name, and you asked me "
2239 "to not replace it")
2242 def test_PUT_NEWFILE_URI(self):
2243 file_contents = "New file contents here\n"
2244 d = self.PUT("/uri", file_contents)
2246 self.failUnless(uri in FakeCHKFileNode.all_contents)
2247 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
2249 return self.GET("/uri/%s" % uri)
2250 d.addCallback(_check)
2252 self.failUnlessEqual(res, file_contents)
2253 d.addCallback(_check2)
2256 def test_PUT_NEWFILE_URI_only_PUT(self):
2257 d = self.PUT("/uri?t=bogus", "")
2258 d.addBoth(self.shouldFail, error.Error,
2259 "PUT_NEWFILE_URI_only_PUT",
2261 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
2264 def test_PUT_NEWFILE_URI_mutable(self):
2265 file_contents = "New file contents here\n"
2266 d = self.PUT("/uri?mutable=true", file_contents)
2267 def _check_mutable(uri):
2270 self.failUnless(IMutableFileURI.providedBy(u))
2271 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
2272 n = self.s.create_node_from_uri(uri)
2273 return n.download_best_version()
2274 d.addCallback(_check_mutable)
2275 def _check2_mutable(data):
2276 self.failUnlessEqual(data, file_contents)
2277 d.addCallback(_check2_mutable)
2281 self.failUnless(uri in FakeCHKFileNode.all_contents)
2282 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
2284 return self.GET("/uri/%s" % uri)
2285 d.addCallback(_check)
2287 self.failUnlessEqual(res, file_contents)
2288 d.addCallback(_check2)
2291 def test_PUT_mkdir(self):
2292 d = self.PUT("/uri?t=mkdir", "")
2294 n = self.s.create_node_from_uri(uri.strip())
2295 d2 = self.failUnlessNodeKeysAre(n, [])
2296 d2.addCallback(lambda res:
2297 self.GET("/uri/%s?t=json" % uri))
2299 d.addCallback(_check)
2300 d.addCallback(self.failUnlessIsEmptyJSON)
2303 def test_POST_check(self):
2304 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
2306 # this returns a string form of the results, which are probably
2307 # None since we're using fake filenodes.
2308 # TODO: verify that the check actually happened, by changing
2309 # FakeCHKFileNode to count how many times .check() has been
2312 d.addCallback(_done)
2315 def test_bad_method(self):
2316 url = self.webish_url + self.public_url + "/foo/bar.txt"
2317 d = self.shouldHTTPError2("test_bad_method",
2318 501, "Not Implemented",
2319 "I don't know how to treat a BOGUS request.",
2320 client.getPage, url, method="BOGUS")
2323 def test_short_url(self):
2324 url = self.webish_url + "/uri"
2325 d = self.shouldHTTPError2("test_short_url", 501, "Not Implemented",
2326 "I don't know how to treat a DELETE request.",
2327 client.getPage, url, method="DELETE")
2330 def test_ophandle_bad(self):
2331 url = self.webish_url + "/operations/bogus?t=status"
2332 d = self.shouldHTTPError2("test_ophandle_bad", 404, "404 Not Found",
2333 "unknown/expired handle 'bogus'",
2334 client.getPage, url)
2337 def test_ophandle_cancel(self):
2338 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
2339 followRedirect=True)
2340 d.addCallback(lambda ignored:
2341 self.GET("/operations/128?t=status&output=JSON"))
2343 data = simplejson.loads(res)
2344 self.failUnless("finished" in data, res)
2345 monitor = self.ws.root.child_operations.handles["128"][0]
2346 d = self.POST("/operations/128?t=cancel&output=JSON")
2348 data = simplejson.loads(res)
2349 self.failUnless("finished" in data, res)
2350 # t=cancel causes the handle to be forgotten
2351 self.failUnless(monitor.is_cancelled())
2352 d.addCallback(_check2)
2354 d.addCallback(_check1)
2355 d.addCallback(lambda ignored:
2356 self.shouldHTTPError2("test_ophandle_cancel",
2357 404, "404 Not Found",
2358 "unknown/expired handle '128'",
2360 "/operations/128?t=status&output=JSON"))
2363 def test_ophandle_retainfor(self):
2364 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
2365 followRedirect=True)
2366 d.addCallback(lambda ignored:
2367 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
2369 data = simplejson.loads(res)
2370 self.failUnless("finished" in data, res)
2371 d.addCallback(_check1)
2372 # the retain-for=0 will cause the handle to be expired very soon
2373 d.addCallback(self.stall, 2.0)
2374 d.addCallback(lambda ignored:
2375 self.shouldHTTPError2("test_ophandle_retainfor",
2376 404, "404 Not Found",
2377 "unknown/expired handle '129'",
2379 "/operations/129?t=status&output=JSON"))
2382 def test_ophandle_release_after_complete(self):
2383 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
2384 followRedirect=True)
2385 d.addCallback(self.wait_for_operation, "130")
2386 d.addCallback(lambda ignored:
2387 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
2388 # the release-after-complete=true will cause the handle to be expired
2389 d.addCallback(lambda ignored:
2390 self.shouldHTTPError2("test_ophandle_release_after_complete",
2391 404, "404 Not Found",
2392 "unknown/expired handle '130'",
2394 "/operations/130?t=status&output=JSON"))
2397 def test_incident(self):
2398 d = self.POST("/report_incident", details="eek")
2400 self.failUnless("Thank you for your report!" in res, res)
2401 d.addCallback(_done)
2404 def test_static(self):
2405 webdir = os.path.join(self.staticdir, "subdir")
2406 fileutil.make_dirs(webdir)
2407 f = open(os.path.join(webdir, "hello.txt"), "wb")
2411 d = self.GET("/static/subdir/hello.txt")
2413 self.failUnlessEqual(res, "hello")
2414 d.addCallback(_check)
2418 class Util(unittest.TestCase):
2419 def test_abbreviate_time(self):
2420 self.failUnlessEqual(common.abbreviate_time(None), "")
2421 self.failUnlessEqual(common.abbreviate_time(1.234), "1.23s")
2422 self.failUnlessEqual(common.abbreviate_time(0.123), "123ms")
2423 self.failUnlessEqual(common.abbreviate_time(0.00123), "1.2ms")
2424 self.failUnlessEqual(common.abbreviate_time(0.000123), "123us")
2426 def test_abbreviate_rate(self):
2427 self.failUnlessEqual(common.abbreviate_rate(None), "")
2428 self.failUnlessEqual(common.abbreviate_rate(1234000), "1.23MBps")
2429 self.failUnlessEqual(common.abbreviate_rate(12340), "12.3kBps")
2430 self.failUnlessEqual(common.abbreviate_rate(123), "123Bps")
2432 def test_abbreviate_size(self):
2433 self.failUnlessEqual(common.abbreviate_size(None), "")
2434 self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
2435 self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
2436 self.failUnlessEqual(common.abbreviate_size(1230), "1.2kB")
2437 self.failUnlessEqual(common.abbreviate_size(123), "123B")