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.ws = s = webish.WebishServer("0")
127 s.setServiceParent(self.s)
128 self.webish_port = port = s.listener._port.getHost().port
129 self.webish_url = "http://localhost:%d" % port
131 l = [ self.s.create_empty_dirnode() for x in range(6) ]
132 d = defer.DeferredList(l)
134 self.public_root = res[0][1]
135 assert interfaces.IDirectoryNode.providedBy(self.public_root), res
136 self.public_url = "/uri/" + self.public_root.get_uri()
137 self.private_root = res[1][1]
141 self._foo_uri = foo.get_uri()
142 self._foo_readonly_uri = foo.get_readonly_uri()
143 # NOTE: we ignore the deferred on all set_uri() calls, because we
144 # know the fake nodes do these synchronously
145 self.public_root.set_uri(u"foo", foo.get_uri())
147 self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
148 foo.set_uri(u"bar.txt", self._bar_txt_uri)
150 foo.set_uri(u"empty", res[3][1].get_uri())
151 sub_uri = res[4][1].get_uri()
152 self._sub_uri = sub_uri
153 foo.set_uri(u"sub", sub_uri)
154 sub = self.s.create_node_from_uri(sub_uri)
156 _ign, n, blocking_uri = self.makefile(1)
157 foo.set_uri(u"blockingfile", blocking_uri)
159 unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
160 # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
161 # still think of it as an umlaut
162 foo.set_uri(unicode_filename, self._bar_txt_uri)
164 _ign, n, baz_file = self.makefile(2)
165 sub.set_uri(u"baz.txt", baz_file)
167 _ign, n, self._bad_file_uri = self.makefile(3)
168 # this uri should not be downloadable
169 del FakeCHKFileNode.all_contents[self._bad_file_uri]
172 self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri())
173 rodir.set_uri(u"nor", baz_file)
178 # public/foo/blockingfile
181 # public/foo/sub/baz.txt
183 # public/reedownlee/nor
184 self.NEWFILE_CONTENTS = "newfile contents\n"
186 return foo.get_metadata_for(u"bar.txt")
188 def _got_metadata(metadata):
189 self._bar_txt_metadata = metadata
190 d.addCallback(_got_metadata)
193 def makefile(self, number):
194 contents = "contents of file %s\n" % number
195 n = create_chk_filenode(self.s, contents)
196 return contents, n, n.get_uri()
199 return self.s.stopService()
201 def failUnlessIsBarDotTxt(self, res):
202 self.failUnlessEqual(res, self.BAR_CONTENTS, res)
204 def failUnlessIsBarJSON(self, res):
205 data = simplejson.loads(res)
206 self.failUnless(isinstance(data, list))
207 self.failUnlessEqual(data[0], u"filenode")
208 self.failUnless(isinstance(data[1], dict))
209 self.failIf(data[1]["mutable"])
210 self.failIf("rw_uri" in data[1]) # immutable
211 self.failUnlessEqual(data[1]["ro_uri"], self._bar_txt_uri)
212 self.failUnlessEqual(data[1]["size"], len(self.BAR_CONTENTS))
214 def failUnlessIsFooJSON(self, res):
215 data = simplejson.loads(res)
216 self.failUnless(isinstance(data, list))
217 self.failUnlessEqual(data[0], "dirnode", res)
218 self.failUnless(isinstance(data[1], dict))
219 self.failUnless(data[1]["mutable"])
220 self.failUnless("rw_uri" in data[1]) # mutable
221 self.failUnlessEqual(data[1]["rw_uri"], self._foo_uri)
222 self.failUnlessEqual(data[1]["ro_uri"], self._foo_readonly_uri)
224 kidnames = sorted([unicode(n) for n in data[1]["children"]])
225 self.failUnlessEqual(kidnames,
226 [u"bar.txt", u"blockingfile", u"empty",
227 u"n\u00fc.txt", u"sub"])
228 kids = dict( [(unicode(name),value)
230 in data[1]["children"].iteritems()] )
231 self.failUnlessEqual(kids[u"sub"][0], "dirnode")
232 self.failUnless("metadata" in kids[u"sub"][1])
233 self.failUnless("ctime" in kids[u"sub"][1]["metadata"])
234 self.failUnless("mtime" in kids[u"sub"][1]["metadata"])
235 self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
236 self.failUnlessEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
237 self.failUnlessEqual(kids[u"bar.txt"][1]["ro_uri"], self._bar_txt_uri)
238 self.failUnlessEqual(kids[u"bar.txt"][1]["metadata"]["ctime"],
239 self._bar_txt_metadata["ctime"])
240 self.failUnlessEqual(kids[u"n\u00fc.txt"][1]["ro_uri"],
243 def GET(self, urlpath, followRedirect=False, return_response=False,
245 # if return_response=True, this fires with (data, statuscode,
246 # respheaders) instead of just data.
247 assert not isinstance(urlpath, unicode)
248 url = self.webish_url + urlpath
249 factory = HTTPClientGETFactory(url, method="GET",
250 followRedirect=followRedirect, **kwargs)
251 reactor.connectTCP("localhost", self.webish_port, factory)
254 return (data, factory.status, factory.response_headers)
256 d.addCallback(_got_data)
257 return factory.deferred
259 def HEAD(self, urlpath, return_response=False, **kwargs):
260 # this requires some surgery, because twisted.web.client doesn't want
261 # to give us back the response headers.
262 factory = HTTPClientHEADFactory(urlpath, method="HEAD", **kwargs)
263 reactor.connectTCP("localhost", self.webish_port, factory)
266 return (data, factory.status, factory.response_headers)
268 d.addCallback(_got_data)
269 return factory.deferred
271 def PUT(self, urlpath, data, **kwargs):
272 url = self.webish_url + urlpath
273 return client.getPage(url, method="PUT", postdata=data, **kwargs)
275 def DELETE(self, urlpath):
276 url = self.webish_url + urlpath
277 return client.getPage(url, method="DELETE")
279 def POST(self, urlpath, followRedirect=False, **fields):
280 url = self.webish_url + urlpath
281 sepbase = "boogabooga"
285 form.append('Content-Disposition: form-data; name="_charset"')
289 for name, value in fields.iteritems():
290 if isinstance(value, tuple):
291 filename, value = value
292 form.append('Content-Disposition: form-data; name="%s"; '
293 'filename="%s"' % (name, filename.encode("utf-8")))
295 form.append('Content-Disposition: form-data; name="%s"' % name)
297 if isinstance(value, unicode):
298 value = value.encode("utf-8")
301 assert isinstance(value, str)
305 body = "\r\n".join(form) + "\r\n"
306 headers = {"content-type": "multipart/form-data; boundary=%s" % sepbase,
308 return client.getPage(url, method="POST", postdata=body,
309 headers=headers, followRedirect=followRedirect)
311 def shouldFail(self, res, expected_failure, which,
312 substring=None, response_substring=None):
313 if isinstance(res, failure.Failure):
314 res.trap(expected_failure)
316 self.failUnless(substring in str(res),
317 "substring '%s' not in '%s'"
318 % (substring, str(res)))
319 if response_substring:
320 self.failUnless(response_substring in res.value.response,
321 "response substring '%s' not in '%s'"
322 % (response_substring, res.value.response))
324 self.fail("%s was supposed to raise %s, not get '%s'" %
325 (which, expected_failure, res))
327 def shouldFail2(self, expected_failure, which, substring,
329 callable, *args, **kwargs):
330 assert substring is None or isinstance(substring, str)
331 assert response_substring is None or isinstance(response_substring, str)
332 d = defer.maybeDeferred(callable, *args, **kwargs)
334 if isinstance(res, failure.Failure):
335 res.trap(expected_failure)
337 self.failUnless(substring in str(res),
338 "%s: substring '%s' not in '%s'"
339 % (which, substring, str(res)))
340 if response_substring:
341 self.failUnless(response_substring in res.value.response,
342 "%s: response substring '%s' not in '%s'"
344 response_substring, res.value.response))
346 self.fail("%s was supposed to raise %s, not get '%s'" %
347 (which, expected_failure, res))
351 def should404(self, res, which):
352 if isinstance(res, failure.Failure):
353 res.trap(error.Error)
354 self.failUnlessEqual(res.value.status, "404")
356 self.fail("%s was supposed to Error(404), not get '%s'" %
359 def shouldHTTPError(self, res, which, code=None, substring=None,
360 response_substring=None):
361 if isinstance(res, failure.Failure):
362 res.trap(error.Error)
364 self.failUnlessEqual(res.value.status, str(code))
366 self.failUnless(substring in str(res),
367 "substring '%s' not in '%s'"
368 % (substring, str(res)))
369 if response_substring:
370 self.failUnless(response_substring in res.value.response,
371 "response substring '%s' not in '%s'"
372 % (response_substring, res.value.response))
374 self.fail("%s was supposed to Error(%s), not get '%s'" %
377 def shouldHTTPError2(self, which,
378 code=None, substring=None, response_substring=None,
379 callable=None, *args, **kwargs):
380 assert substring is None or isinstance(substring, str)
382 d = defer.maybeDeferred(callable, *args, **kwargs)
383 d.addBoth(self.shouldHTTPError, which,
384 code, substring, response_substring)
388 class Web(WebMixin, testutil.StallMixin, unittest.TestCase):
389 def test_create(self):
392 def test_welcome(self):
395 self.failUnless('Welcome To AllMyData' in res)
396 self.failUnless('Tahoe' in res)
398 self.s.basedir = 'web/test_welcome'
399 fileutil.make_dirs("web/test_welcome")
400 fileutil.make_dirs("web/test_welcome/private")
402 d.addCallback(_check)
405 def test_provisioning_math(self):
406 self.failUnlessEqual(provisioning.binomial(10, 0), 1)
407 self.failUnlessEqual(provisioning.binomial(10, 1), 10)
408 self.failUnlessEqual(provisioning.binomial(10, 2), 45)
409 self.failUnlessEqual(provisioning.binomial(10, 9), 10)
410 self.failUnlessEqual(provisioning.binomial(10, 10), 1)
412 def test_provisioning(self):
413 d = self.GET("/provisioning/")
415 self.failUnless('Tahoe Provisioning Tool' in res)
416 fields = {'filled': True,
417 "num_users": int(50e3),
418 "files_per_user": 1000,
419 "space_per_user": int(1e9),
420 "sharing_ratio": 1.0,
421 "encoding_parameters": "3-of-10-5",
423 "ownership_mode": "A",
424 "download_rate": 100,
429 return self.POST("/provisioning/", **fields)
431 d.addCallback(_check)
433 self.failUnless('Tahoe Provisioning Tool' in res)
434 self.failUnless("Share space consumed: 167.01TB" in res)
436 fields = {'filled': True,
437 "num_users": int(50e6),
438 "files_per_user": 1000,
439 "space_per_user": int(5e9),
440 "sharing_ratio": 1.0,
441 "encoding_parameters": "25-of-100-50",
442 "num_servers": 30000,
443 "ownership_mode": "E",
444 "drive_failure_model": "U",
446 "download_rate": 1000,
451 return self.POST("/provisioning/", **fields)
452 d.addCallback(_check2)
454 self.failUnless("Share space consumed: huge!" in res)
455 fields = {'filled': True}
456 return self.POST("/provisioning/", **fields)
457 d.addCallback(_check3)
459 self.failUnless("Share space consumed:" in res)
460 d.addCallback(_check4)
463 def test_status(self):
464 dl_num = self.s.list_all_download_statuses()[0].get_counter()
465 ul_num = self.s.list_all_upload_statuses()[0].get_counter()
466 mu_num = self.s.list_all_mapupdate_statuses()[0].get_counter()
467 pub_num = self.s.list_all_publish_statuses()[0].get_counter()
468 ret_num = self.s.list_all_retrieve_statuses()[0].get_counter()
469 d = self.GET("/status", followRedirect=True)
471 self.failUnless('Upload and Download Status' in res, res)
472 self.failUnless('"down-%d"' % dl_num in res, res)
473 self.failUnless('"up-%d"' % ul_num in res, res)
474 self.failUnless('"mapupdate-%d"' % mu_num in res, res)
475 self.failUnless('"publish-%d"' % pub_num in res, res)
476 self.failUnless('"retrieve-%d"' % ret_num in res, res)
477 d.addCallback(_check)
478 d.addCallback(lambda res: self.GET("/status/?t=json"))
479 def _check_json(res):
480 data = simplejson.loads(res)
481 self.failUnless(isinstance(data, dict))
482 active = data["active"]
483 # TODO: test more. We need a way to fake an active operation
485 d.addCallback(_check_json)
487 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
489 self.failUnless("File Download Status" in res, res)
490 d.addCallback(_check_dl)
491 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
493 self.failUnless("File Upload Status" in res, res)
494 d.addCallback(_check_ul)
495 d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num))
496 def _check_mapupdate(res):
497 self.failUnless("Mutable File Servermap Update Status" in res, res)
498 d.addCallback(_check_mapupdate)
499 d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num))
500 def _check_publish(res):
501 self.failUnless("Mutable File Publish Status" in res, res)
502 d.addCallback(_check_publish)
503 d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num))
504 def _check_retrieve(res):
505 self.failUnless("Mutable File Retrieve Status" in res, res)
506 d.addCallback(_check_retrieve)
510 def test_status_numbers(self):
511 drrm = status.DownloadResultsRendererMixin()
512 self.failUnlessEqual(drrm.render_time(None, None), "")
513 self.failUnlessEqual(drrm.render_time(None, 2.5), "2.50s")
514 self.failUnlessEqual(drrm.render_time(None, 0.25), "250ms")
515 self.failUnlessEqual(drrm.render_time(None, 0.0021), "2.1ms")
516 self.failUnlessEqual(drrm.render_time(None, 0.000123), "123us")
517 self.failUnlessEqual(drrm.render_rate(None, None), "")
518 self.failUnlessEqual(drrm.render_rate(None, 2500000), "2.50MBps")
519 self.failUnlessEqual(drrm.render_rate(None, 30100), "30.1kBps")
520 self.failUnlessEqual(drrm.render_rate(None, 123), "123Bps")
522 urrm = status.UploadResultsRendererMixin()
523 self.failUnlessEqual(urrm.render_time(None, None), "")
524 self.failUnlessEqual(urrm.render_time(None, 2.5), "2.50s")
525 self.failUnlessEqual(urrm.render_time(None, 0.25), "250ms")
526 self.failUnlessEqual(urrm.render_time(None, 0.0021), "2.1ms")
527 self.failUnlessEqual(urrm.render_time(None, 0.000123), "123us")
528 self.failUnlessEqual(urrm.render_rate(None, None), "")
529 self.failUnlessEqual(urrm.render_rate(None, 2500000), "2.50MBps")
530 self.failUnlessEqual(urrm.render_rate(None, 30100), "30.1kBps")
531 self.failUnlessEqual(urrm.render_rate(None, 123), "123Bps")
533 def test_GET_FILEURL(self):
534 d = self.GET(self.public_url + "/foo/bar.txt")
535 d.addCallback(self.failUnlessIsBarDotTxt)
538 def test_GET_FILEURL_range(self):
539 headers = {"range": "bytes=1-10"}
540 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
541 return_response=True)
542 def _got((res, status, headers)):
543 self.failUnlessEqual(int(status), 206)
544 self.failUnless(headers.has_key("content-range"))
545 self.failUnlessEqual(headers["content-range"][0],
546 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
547 self.failUnlessEqual(res, self.BAR_CONTENTS[1:11])
551 def test_HEAD_FILEURL_range(self):
552 headers = {"range": "bytes=1-10"}
553 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
554 return_response=True)
555 def _got((res, status, headers)):
556 self.failUnlessEqual(res, "")
557 self.failUnlessEqual(int(status), 206)
558 self.failUnless(headers.has_key("content-range"))
559 self.failUnlessEqual(headers["content-range"][0],
560 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
564 def test_GET_FILEURL_range_bad(self):
565 headers = {"range": "BOGUS=fizbop-quarnak"}
566 d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_bad",
568 "Syntactically invalid http range header",
569 self.GET, self.public_url + "/foo/bar.txt",
573 def test_HEAD_FILEURL(self):
574 d = self.HEAD(self.public_url + "/foo/bar.txt", return_response=True)
575 def _got((res, status, headers)):
576 self.failUnlessEqual(res, "")
577 self.failUnlessEqual(headers["content-length"][0],
578 str(len(self.BAR_CONTENTS)))
579 self.failUnlessEqual(headers["content-type"], ["text/plain"])
583 def test_GET_FILEURL_named(self):
584 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
585 base2 = "/named/%s" % urllib.quote(self._bar_txt_uri)
586 d = self.GET(base + "/@@name=/blah.txt")
587 d.addCallback(self.failUnlessIsBarDotTxt)
588 d.addCallback(lambda res: self.GET(base + "/blah.txt"))
589 d.addCallback(self.failUnlessIsBarDotTxt)
590 d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt"))
591 d.addCallback(self.failUnlessIsBarDotTxt)
592 d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt"))
593 d.addCallback(self.failUnlessIsBarDotTxt)
594 save_url = base + "?save=true&filename=blah.txt"
595 d.addCallback(lambda res: self.GET(save_url))
596 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
597 u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
598 u_fn_e = urllib.quote(u_filename.encode("utf-8"))
599 u_url = base + "?save=true&filename=" + u_fn_e
600 d.addCallback(lambda res: self.GET(u_url))
601 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
604 def test_PUT_FILEURL_named_bad(self):
605 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
606 d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad",
608 "/file can only be used with GET or HEAD",
609 self.PUT, base + "/@@name=/blah.txt", "")
612 def test_GET_DIRURL_named_bad(self):
613 base = "/file/%s" % urllib.quote(self._foo_uri)
614 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
617 self.GET, base + "/@@name=/blah.txt")
620 def test_GET_slash_file_bad(self):
621 d = self.shouldFail2(error.Error, "test_GET_slash_file_bad",
623 "/file must be followed by a file-cap and a name",
627 def test_GET_unhandled_URI_named(self):
628 contents, n, newuri = self.makefile(12)
629 verifier_cap = n.get_verifier().to_string()
630 base = "/file/%s" % urllib.quote(verifier_cap)
631 # client.create_node_from_uri() can't handle verify-caps
632 d = self.shouldFail2(error.Error, "GET_unhandled_URI_named",
634 "is not a valid file- or directory- cap",
638 def test_GET_unhandled_URI(self):
639 contents, n, newuri = self.makefile(12)
640 verifier_cap = n.get_verifier().to_string()
641 base = "/uri/%s" % urllib.quote(verifier_cap)
642 # client.create_node_from_uri() can't handle verify-caps
643 d = self.shouldFail2(error.Error, "test_GET_unhandled_URI",
645 "is not a valid file- or directory- cap",
649 def test_GET_FILE_URI(self):
650 base = "/uri/%s" % urllib.quote(self._bar_txt_uri)
652 d.addCallback(self.failUnlessIsBarDotTxt)
655 def test_GET_FILE_URI_badchild(self):
656 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
657 errmsg = "Files have no children, certainly not named 'boguschild'"
658 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
659 "400 Bad Request", errmsg,
663 def test_PUT_FILE_URI_badchild(self):
664 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
665 errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory"
666 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
667 "400 Bad Request", errmsg,
671 def test_GET_FILEURL_save(self):
672 d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true")
673 # TODO: look at the headers, expect a Content-Disposition: attachment
675 d.addCallback(self.failUnlessIsBarDotTxt)
678 def test_GET_FILEURL_missing(self):
679 d = self.GET(self.public_url + "/foo/missing")
680 d.addBoth(self.should404, "test_GET_FILEURL_missing")
683 def test_PUT_NEWFILEURL(self):
684 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
685 # TODO: we lose the response code, so we can't check this
686 #self.failUnlessEqual(responsecode, 201)
687 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
688 d.addCallback(lambda res:
689 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
690 self.NEWFILE_CONTENTS))
693 def test_PUT_NEWFILEURL_range_bad(self):
694 headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
695 target = self.public_url + "/foo/new.txt"
696 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad",
697 "501 Not Implemented",
698 "Content-Range in PUT not yet supported",
699 # (and certainly not for immutable files)
700 self.PUT, target, self.NEWFILE_CONTENTS[1:11],
702 d.addCallback(lambda res:
703 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
706 def test_PUT_NEWFILEURL_mutable(self):
707 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
708 self.NEWFILE_CONTENTS)
709 # TODO: we lose the response code, so we can't check this
710 #self.failUnlessEqual(responsecode, 201)
712 u = uri.from_string_mutable_filenode(res)
713 self.failUnless(u.is_mutable())
714 self.failIf(u.is_readonly())
716 d.addCallback(_check_uri)
717 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
718 d.addCallback(lambda res:
719 self.failUnlessMutableChildContentsAre(self._foo_node,
721 self.NEWFILE_CONTENTS))
724 def test_PUT_NEWFILEURL_mutable_toobig(self):
725 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_mutable_toobig",
726 "413 Request Entity Too Large",
727 "SDMF is limited to one segment, and 10001 > 10000",
729 self.public_url + "/foo/new.txt?mutable=true",
730 "b" * (self.s.MUTABLE_SIZELIMIT+1))
733 def test_PUT_NEWFILEURL_replace(self):
734 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
735 # TODO: we lose the response code, so we can't check this
736 #self.failUnlessEqual(responsecode, 200)
737 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
738 d.addCallback(lambda res:
739 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
740 self.NEWFILE_CONTENTS))
743 def test_PUT_NEWFILEURL_bad_t(self):
744 d = self.shouldFail2(error.Error, "PUT_bad_t", "400 Bad Request",
745 "PUT to a file: bad t=bogus",
746 self.PUT, self.public_url + "/foo/bar.txt?t=bogus",
750 def test_PUT_NEWFILEURL_no_replace(self):
751 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
752 self.NEWFILE_CONTENTS)
753 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
755 "There was already a child by that name, and you asked me "
759 def test_PUT_NEWFILEURL_mkdirs(self):
760 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
762 d.addCallback(self.failUnlessURIMatchesChild, fn, u"newdir/new.txt")
763 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
764 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
765 d.addCallback(lambda res:
766 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
767 self.NEWFILE_CONTENTS))
770 def test_PUT_NEWFILEURL_blocked(self):
771 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
772 self.NEWFILE_CONTENTS)
773 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
775 "Unable to create directory 'blockingfile': a file was in the way")
778 def test_DELETE_FILEURL(self):
779 d = self.DELETE(self.public_url + "/foo/bar.txt")
780 d.addCallback(lambda res:
781 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
784 def test_DELETE_FILEURL_missing(self):
785 d = self.DELETE(self.public_url + "/foo/missing")
786 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
789 def test_DELETE_FILEURL_missing2(self):
790 d = self.DELETE(self.public_url + "/missing/missing")
791 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
794 def test_GET_FILEURL_json(self):
795 # twisted.web.http.parse_qs ignores any query args without an '=', so
796 # I can't do "GET /path?json", I have to do "GET /path/t=json"
797 # instead. This may make it tricky to emulate the S3 interface
799 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
800 d.addCallback(self.failUnlessIsBarJSON)
803 def test_GET_FILEURL_json_missing(self):
804 d = self.GET(self.public_url + "/foo/missing?json")
805 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
808 def test_GET_FILEURL_uri(self):
809 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
811 self.failUnlessEqual(res, self._bar_txt_uri)
812 d.addCallback(_check)
813 d.addCallback(lambda res:
814 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
816 # for now, for files, uris and readonly-uris are the same
817 self.failUnlessEqual(res, self._bar_txt_uri)
818 d.addCallback(_check2)
821 def test_GET_FILEURL_badtype(self):
822 d = self.shouldHTTPError2("GET t=bogus", 400, "Bad Request",
825 self.public_url + "/foo/bar.txt?t=bogus")
828 def test_GET_FILEURL_uri_missing(self):
829 d = self.GET(self.public_url + "/foo/missing?t=uri")
830 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
833 def test_GET_DIRURL(self):
834 # the addSlash means we get a redirect here
835 # from /uri/$URI/foo/ , we need ../../../ to get back to the root
837 d = self.GET(self.public_url + "/foo", followRedirect=True)
839 self.failUnless(('<a href="%s">Return to Welcome page' % ROOT)
841 # the FILE reference points to a URI, but it should end in bar.txt
842 bar_url = ("%s/file/%s/@@named=/bar.txt" %
843 (ROOT, urllib.quote(self._bar_txt_uri)))
844 get_bar = "".join([r'<td>',
845 r'<a href="%s">bar.txt</a>' % bar_url,
848 r'\s+<td>%d</td>' % len(self.BAR_CONTENTS),
850 self.failUnless(re.search(get_bar, res), res)
851 for line in res.split("\n"):
852 # find the line that contains the delete button for bar.txt
853 if ("form action" in line and
854 'value="delete"' in line and
855 'value="bar.txt"' in line):
856 # the form target should use a relative URL
857 foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
858 self.failUnless(('action="%s"' % foo_url) in line, line)
859 # and the when_done= should too
860 #done_url = urllib.quote(???)
861 #self.failUnless(('name="when_done" value="%s"' % done_url)
865 self.fail("unable to find delete-bar.txt line", res)
867 # the DIR reference just points to a URI
868 sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
869 get_sub = ((r'<td><a href="%s">sub</a></td>' % sub_url)
870 + r'\s+<td>DIR</td>')
871 self.failUnless(re.search(get_sub, res), res)
872 d.addCallback(_check)
874 # look at a directory which is readonly
875 d.addCallback(lambda res:
876 self.GET(self.public_url + "/reedownlee", followRedirect=True))
878 self.failUnless("(readonly)" in res, res)
879 self.failIf("Upload a file" in res, res)
880 d.addCallback(_check2)
882 # and at a directory that contains a readonly directory
883 d.addCallback(lambda res:
884 self.GET(self.public_url, followRedirect=True))
886 self.failUnless(re.search(r'<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a>'
887 '</td>\s+<td>DIR-RO</td>', res))
888 d.addCallback(_check3)
892 def test_GET_DIRURL_badtype(self):
893 d = self.shouldHTTPError2("test_GET_DIRURL_badtype",
897 self.public_url + "/foo?t=bogus")
900 def test_GET_DIRURL_json(self):
901 d = self.GET(self.public_url + "/foo?t=json")
902 d.addCallback(self.failUnlessIsFooJSON)
906 def test_POST_DIRURL_manifest_no_ophandle(self):
907 d = self.shouldFail2(error.Error,
908 "test_POST_DIRURL_manifest_no_ophandle",
910 "slow operation requires ophandle=",
911 self.POST, self.public_url, t="start-manifest")
914 def test_POST_DIRURL_manifest(self):
915 d = defer.succeed(None)
916 def getman(ignored, output):
917 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
919 d.addCallback(self.wait_for_operation, "125")
920 d.addCallback(self.get_operation_results, "125", output)
922 d.addCallback(getman, None)
923 def _got_html(manifest):
924 self.failUnless("Manifest of SI=" in manifest)
925 self.failUnless("<td>sub</td>" in manifest)
926 self.failUnless(self._sub_uri in manifest)
927 self.failUnless("<td>sub/baz.txt</td>" in manifest)
928 d.addCallback(_got_html)
930 # both t=status and unadorned GET should be identical
931 d.addCallback(lambda res: self.GET("/operations/125"))
932 d.addCallback(_got_html)
934 d.addCallback(getman, "html")
935 d.addCallback(_got_html)
936 d.addCallback(getman, "text")
937 def _got_text(manifest):
938 self.failUnless("\nsub " + self._sub_uri + "\n" in manifest)
939 self.failUnless("\nsub/baz.txt URI:CHK:" in manifest)
940 d.addCallback(_got_text)
941 d.addCallback(getman, "JSON")
942 def _got_json(manifest):
943 data = manifest["manifest"]
945 for (path_list, cap) in data:
946 got[tuple(path_list)] = cap
947 self.failUnlessEqual(got[(u"sub",)], self._sub_uri)
948 self.failUnless((u"sub",u"baz.txt") in got)
949 d.addCallback(_got_json)
952 def test_POST_DIRURL_deepsize_no_ophandle(self):
953 d = self.shouldFail2(error.Error,
954 "test_POST_DIRURL_deepsize_no_ophandle",
956 "slow operation requires ophandle=",
957 self.POST, self.public_url, t="start-deep-size")
960 def test_POST_DIRURL_deepsize(self):
961 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
963 d.addCallback(self.wait_for_operation, "126")
964 d.addCallback(self.get_operation_results, "126", "json")
966 self.failUnlessEqual(data["finished"], True)
968 self.failUnless(size > 1000)
969 d.addCallback(_got_json)
970 d.addCallback(self.get_operation_results, "126", "text")
972 mo = re.search(r'^size: (\d+)$', res, re.M)
973 self.failUnless(mo, res)
974 size = int(mo.group(1))
975 # with directories, the size varies.
976 self.failUnless(size > 1000)
977 d.addCallback(_got_text)
980 def test_POST_DIRURL_deepstats_no_ophandle(self):
981 d = self.shouldFail2(error.Error,
982 "test_POST_DIRURL_deepstats_no_ophandle",
984 "slow operation requires ophandle=",
985 self.POST, self.public_url, t="start-deep-stats")
988 def test_POST_DIRURL_deepstats(self):
989 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
991 d.addCallback(self.wait_for_operation, "127")
992 d.addCallback(self.get_operation_results, "127", "json")
993 def _got_json(stats):
994 expected = {"count-immutable-files": 3,
995 "count-mutable-files": 0,
996 "count-literal-files": 0,
998 "count-directories": 3,
999 "size-immutable-files": 57,
1000 "size-literal-files": 0,
1001 #"size-directories": 1912, # varies
1002 #"largest-directory": 1590,
1003 "largest-directory-children": 5,
1004 "largest-immutable-file": 19,
1006 for k,v in expected.iteritems():
1007 self.failUnlessEqual(stats[k], v,
1008 "stats[%s] was %s, not %s" %
1010 self.failUnlessEqual(stats["size-files-histogram"],
1012 d.addCallback(_got_json)
1015 def test_GET_DIRURL_uri(self):
1016 d = self.GET(self.public_url + "/foo?t=uri")
1018 self.failUnlessEqual(res, self._foo_uri)
1019 d.addCallback(_check)
1022 def test_GET_DIRURL_readonly_uri(self):
1023 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1025 self.failUnlessEqual(res, self._foo_readonly_uri)
1026 d.addCallback(_check)
1029 def test_PUT_NEWDIRURL(self):
1030 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1031 d.addCallback(lambda res:
1032 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1033 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1034 d.addCallback(self.failUnlessNodeKeysAre, [])
1037 def test_PUT_NEWDIRURL_exists(self):
1038 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1039 d.addCallback(lambda res:
1040 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1041 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1042 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1045 def test_PUT_NEWDIRURL_blocked(self):
1046 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
1047 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
1049 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
1050 d.addCallback(lambda res:
1051 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1052 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1053 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1056 def test_PUT_NEWDIRURL_mkdir_p(self):
1057 d = defer.succeed(None)
1058 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
1059 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
1060 d.addCallback(lambda res: self._foo_node.get(u"mkp"))
1061 def mkdir_p(mkpnode):
1062 url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
1064 def made_subsub(ssuri):
1065 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
1066 d.addCallback(lambda ssnode: self.failUnlessEqual(ssnode.get_uri(), ssuri))
1068 d.addCallback(lambda uri2: self.failUnlessEqual(uri2, ssuri))
1070 d.addCallback(made_subsub)
1072 d.addCallback(mkdir_p)
1075 def test_PUT_NEWDIRURL_mkdirs(self):
1076 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1077 d.addCallback(lambda res:
1078 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1079 d.addCallback(lambda res:
1080 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1081 d.addCallback(lambda res:
1082 self._foo_node.get_child_at_path(u"subdir/newdir"))
1083 d.addCallback(self.failUnlessNodeKeysAre, [])
1086 def test_DELETE_DIRURL(self):
1087 d = self.DELETE(self.public_url + "/foo")
1088 d.addCallback(lambda res:
1089 self.failIfNodeHasChild(self.public_root, u"foo"))
1092 def test_DELETE_DIRURL_missing(self):
1093 d = self.DELETE(self.public_url + "/foo/missing")
1094 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
1095 d.addCallback(lambda res:
1096 self.failUnlessNodeHasChild(self.public_root, u"foo"))
1099 def test_DELETE_DIRURL_missing2(self):
1100 d = self.DELETE(self.public_url + "/missing")
1101 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
1104 def dump_root(self):
1106 w = webish.DirnodeWalkerMixin()
1107 def visitor(childpath, childnode, metadata):
1109 d = w.walk(self.public_root, visitor)
1112 def failUnlessNodeKeysAre(self, node, expected_keys):
1113 for k in expected_keys:
1114 assert isinstance(k, unicode)
1116 def _check(children):
1117 self.failUnlessEqual(sorted(children.keys()), sorted(expected_keys))
1118 d.addCallback(_check)
1120 def failUnlessNodeHasChild(self, node, name):
1121 assert isinstance(name, unicode)
1123 def _check(children):
1124 self.failUnless(name in children)
1125 d.addCallback(_check)
1127 def failIfNodeHasChild(self, node, name):
1128 assert isinstance(name, unicode)
1130 def _check(children):
1131 self.failIf(name in children)
1132 d.addCallback(_check)
1135 def failUnlessChildContentsAre(self, node, name, expected_contents):
1136 assert isinstance(name, unicode)
1137 d = node.get_child_at_path(name)
1138 d.addCallback(lambda node: node.download_to_data())
1139 def _check(contents):
1140 self.failUnlessEqual(contents, expected_contents)
1141 d.addCallback(_check)
1144 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
1145 assert isinstance(name, unicode)
1146 d = node.get_child_at_path(name)
1147 d.addCallback(lambda node: node.download_best_version())
1148 def _check(contents):
1149 self.failUnlessEqual(contents, expected_contents)
1150 d.addCallback(_check)
1153 def failUnlessChildURIIs(self, node, name, expected_uri):
1154 assert isinstance(name, unicode)
1155 d = node.get_child_at_path(name)
1157 self.failUnlessEqual(child.get_uri(), expected_uri.strip())
1158 d.addCallback(_check)
1161 def failUnlessURIMatchesChild(self, got_uri, node, name):
1162 assert isinstance(name, unicode)
1163 d = node.get_child_at_path(name)
1165 self.failUnlessEqual(got_uri.strip(), child.get_uri())
1166 d.addCallback(_check)
1169 def failUnlessCHKURIHasContents(self, got_uri, contents):
1170 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
1172 def test_POST_upload(self):
1173 d = self.POST(self.public_url + "/foo", t="upload",
1174 file=("new.txt", self.NEWFILE_CONTENTS))
1176 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1177 d.addCallback(lambda res:
1178 self.failUnlessChildContentsAre(fn, u"new.txt",
1179 self.NEWFILE_CONTENTS))
1182 def test_POST_upload_unicode(self):
1183 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1184 d = self.POST(self.public_url + "/foo", t="upload",
1185 file=(filename, self.NEWFILE_CONTENTS))
1187 d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1188 d.addCallback(lambda res:
1189 self.failUnlessChildContentsAre(fn, filename,
1190 self.NEWFILE_CONTENTS))
1191 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1192 d.addCallback(lambda res: self.GET(target_url))
1193 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1194 self.NEWFILE_CONTENTS,
1198 def test_POST_upload_unicode_named(self):
1199 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1200 d = self.POST(self.public_url + "/foo", t="upload",
1202 file=("overridden", self.NEWFILE_CONTENTS))
1204 d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1205 d.addCallback(lambda res:
1206 self.failUnlessChildContentsAre(fn, filename,
1207 self.NEWFILE_CONTENTS))
1208 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1209 d.addCallback(lambda res: self.GET(target_url))
1210 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1211 self.NEWFILE_CONTENTS,
1215 def test_POST_upload_no_link(self):
1216 d = self.POST("/uri", t="upload",
1217 file=("new.txt", self.NEWFILE_CONTENTS))
1218 def _check_upload_results(page):
1219 # this should be a page which describes the results of the upload
1220 # that just finished.
1221 self.failUnless("Upload Results:" in page)
1222 self.failUnless("URI:" in page)
1223 uri_re = re.compile("URI: <tt><span>(.*)</span>")
1224 mo = uri_re.search(page)
1225 self.failUnless(mo, page)
1226 new_uri = mo.group(1)
1228 d.addCallback(_check_upload_results)
1229 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1232 def test_POST_upload_no_link_whendone(self):
1233 d = self.POST("/uri", t="upload", when_done="/",
1234 file=("new.txt", self.NEWFILE_CONTENTS))
1235 d.addBoth(self.shouldRedirect, "/")
1238 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
1239 d = defer.maybeDeferred(callable, *args, **kwargs)
1241 if isinstance(res, failure.Failure):
1242 res.trap(error.PageRedirect)
1243 statuscode = res.value.status
1244 target = res.value.location
1245 return checker(statuscode, target)
1246 self.fail("%s: callable was supposed to redirect, not return '%s'"
1251 def test_POST_upload_no_link_whendone_results(self):
1252 def check(statuscode, target):
1253 self.failUnlessEqual(statuscode, str(http.FOUND))
1254 self.failUnless(target.startswith(self.webish_url), target)
1255 return client.getPage(target, method="GET")
1256 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
1258 self.POST, "/uri", t="upload",
1259 when_done="/uri/%(uri)s",
1260 file=("new.txt", self.NEWFILE_CONTENTS))
1261 d.addCallback(lambda res:
1262 self.failUnlessEqual(res, self.NEWFILE_CONTENTS))
1265 def test_POST_upload_no_link_mutable(self):
1266 d = self.POST("/uri", t="upload", mutable="true",
1267 file=("new.txt", self.NEWFILE_CONTENTS))
1268 def _check(new_uri):
1269 new_uri = new_uri.strip()
1270 self.new_uri = new_uri
1272 self.failUnless(IMutableFileURI.providedBy(u))
1273 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
1274 n = self.s.create_node_from_uri(new_uri)
1275 return n.download_best_version()
1276 d.addCallback(_check)
1278 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1279 return self.GET("/uri/%s" % urllib.quote(self.new_uri))
1280 d.addCallback(_check2)
1282 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1283 return self.GET("/file/%s" % urllib.quote(self.new_uri))
1284 d.addCallback(_check3)
1286 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1287 d.addCallback(_check4)
1290 def test_POST_upload_no_link_mutable_toobig(self):
1291 d = self.shouldFail2(error.Error,
1292 "test_POST_upload_no_link_mutable_toobig",
1293 "413 Request Entity Too Large",
1294 "SDMF is limited to one segment, and 10001 > 10000",
1296 "/uri", t="upload", mutable="true",
1298 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1301 def test_POST_upload_mutable(self):
1302 # this creates a mutable file
1303 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
1304 file=("new.txt", self.NEWFILE_CONTENTS))
1306 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1307 d.addCallback(lambda res:
1308 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1309 self.NEWFILE_CONTENTS))
1310 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1312 self.failUnless(IMutableFileNode.providedBy(newnode))
1313 self.failUnless(newnode.is_mutable())
1314 self.failIf(newnode.is_readonly())
1315 self._mutable_node = newnode
1316 self._mutable_uri = newnode.get_uri()
1319 # now upload it again and make sure that the URI doesn't change
1320 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
1321 d.addCallback(lambda res:
1322 self.POST(self.public_url + "/foo", t="upload",
1324 file=("new.txt", NEWER_CONTENTS)))
1325 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1326 d.addCallback(lambda res:
1327 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1329 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1331 self.failUnless(IMutableFileNode.providedBy(newnode))
1332 self.failUnless(newnode.is_mutable())
1333 self.failIf(newnode.is_readonly())
1334 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1335 d.addCallback(_got2)
1337 # upload a second time, using PUT instead of POST
1338 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
1339 d.addCallback(lambda res:
1340 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
1341 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1342 d.addCallback(lambda res:
1343 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1346 # finally list the directory, since mutable files are displayed
1347 # slightly differently
1349 d.addCallback(lambda res:
1350 self.GET(self.public_url + "/foo/",
1351 followRedirect=True))
1352 def _check_page(res):
1353 # TODO: assert more about the contents
1354 self.failUnless("SSK" in res)
1356 d.addCallback(_check_page)
1358 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1360 self.failUnless(IMutableFileNode.providedBy(newnode))
1361 self.failUnless(newnode.is_mutable())
1362 self.failIf(newnode.is_readonly())
1363 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1364 d.addCallback(_got3)
1366 # look at the JSON form of the enclosing directory
1367 d.addCallback(lambda res:
1368 self.GET(self.public_url + "/foo/?t=json",
1369 followRedirect=True))
1370 def _check_page_json(res):
1371 parsed = simplejson.loads(res)
1372 self.failUnlessEqual(parsed[0], "dirnode")
1373 children = dict( [(unicode(name),value)
1375 in parsed[1]["children"].iteritems()] )
1376 self.failUnless("new.txt" in children)
1377 new_json = children["new.txt"]
1378 self.failUnlessEqual(new_json[0], "filenode")
1379 self.failUnless(new_json[1]["mutable"])
1380 self.failUnlessEqual(new_json[1]["rw_uri"], self._mutable_uri)
1381 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1382 self.failUnlessEqual(new_json[1]["ro_uri"], ro_uri)
1383 d.addCallback(_check_page_json)
1385 # and the JSON form of the file
1386 d.addCallback(lambda res:
1387 self.GET(self.public_url + "/foo/new.txt?t=json"))
1388 def _check_file_json(res):
1389 parsed = simplejson.loads(res)
1390 self.failUnlessEqual(parsed[0], "filenode")
1391 self.failUnless(parsed[1]["mutable"])
1392 self.failUnlessEqual(parsed[1]["rw_uri"], self._mutable_uri)
1393 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1394 self.failUnlessEqual(parsed[1]["ro_uri"], ro_uri)
1395 d.addCallback(_check_file_json)
1397 # and look at t=uri and t=readonly-uri
1398 d.addCallback(lambda res:
1399 self.GET(self.public_url + "/foo/new.txt?t=uri"))
1400 d.addCallback(lambda res: self.failUnlessEqual(res, self._mutable_uri))
1401 d.addCallback(lambda res:
1402 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
1403 def _check_ro_uri(res):
1404 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1405 self.failUnlessEqual(res, ro_uri)
1406 d.addCallback(_check_ro_uri)
1408 # make sure we can get to it from /uri/URI
1409 d.addCallback(lambda res:
1410 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
1411 d.addCallback(lambda res:
1412 self.failUnlessEqual(res, NEW2_CONTENTS))
1414 # and that HEAD computes the size correctly
1415 d.addCallback(lambda res:
1416 self.HEAD(self.public_url + "/foo/new.txt",
1417 return_response=True))
1418 def _got_headers((res, status, headers)):
1419 self.failUnlessEqual(res, "")
1420 self.failUnlessEqual(headers["content-length"][0],
1421 str(len(NEW2_CONTENTS)))
1422 self.failUnlessEqual(headers["content-type"], ["text/plain"])
1423 d.addCallback(_got_headers)
1425 # make sure that size errors are displayed correctly for overwrite
1426 d.addCallback(lambda res:
1427 self.shouldFail2(error.Error,
1428 "test_POST_upload_mutable-toobig",
1429 "413 Request Entity Too Large",
1430 "SDMF is limited to one segment, and 10001 > 10000",
1432 self.public_url + "/foo", t="upload",
1435 "b" * (self.s.MUTABLE_SIZELIMIT+1)),
1438 d.addErrback(self.dump_error)
1441 def test_POST_upload_mutable_toobig(self):
1442 d = self.shouldFail2(error.Error,
1443 "test_POST_upload_no_link_mutable_toobig",
1444 "413 Request Entity Too Large",
1445 "SDMF is limited to one segment, and 10001 > 10000",
1447 self.public_url + "/foo",
1448 t="upload", mutable="true",
1450 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1453 def dump_error(self, f):
1454 # if the web server returns an error code (like 400 Bad Request),
1455 # web.client.getPage puts the HTTP response body into the .response
1456 # attribute of the exception object that it gives back. It does not
1457 # appear in the Failure's repr(), so the ERROR that trial displays
1458 # will be rather terse and unhelpful. addErrback this method to the
1459 # end of your chain to get more information out of these errors.
1460 if f.check(error.Error):
1461 print "web.error.Error:"
1463 print f.value.response
1466 def test_POST_upload_replace(self):
1467 d = self.POST(self.public_url + "/foo", t="upload",
1468 file=("bar.txt", self.NEWFILE_CONTENTS))
1470 d.addCallback(self.failUnlessURIMatchesChild, fn, u"bar.txt")
1471 d.addCallback(lambda res:
1472 self.failUnlessChildContentsAre(fn, u"bar.txt",
1473 self.NEWFILE_CONTENTS))
1476 def test_POST_upload_no_replace_ok(self):
1477 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1478 file=("new.txt", self.NEWFILE_CONTENTS))
1479 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
1480 d.addCallback(lambda res: self.failUnlessEqual(res,
1481 self.NEWFILE_CONTENTS))
1484 def test_POST_upload_no_replace_queryarg(self):
1485 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1486 file=("bar.txt", self.NEWFILE_CONTENTS))
1487 d.addBoth(self.shouldFail, error.Error,
1488 "POST_upload_no_replace_queryarg",
1490 "There was already a child by that name, and you asked me "
1491 "to not replace it")
1492 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1493 d.addCallback(self.failUnlessIsBarDotTxt)
1496 def test_POST_upload_no_replace_field(self):
1497 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
1498 file=("bar.txt", self.NEWFILE_CONTENTS))
1499 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
1501 "There was already a child by that name, and you asked me "
1502 "to not replace it")
1503 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1504 d.addCallback(self.failUnlessIsBarDotTxt)
1507 def test_POST_upload_whendone(self):
1508 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
1509 file=("new.txt", self.NEWFILE_CONTENTS))
1510 d.addBoth(self.shouldRedirect, "/THERE")
1512 d.addCallback(lambda res:
1513 self.failUnlessChildContentsAre(fn, u"new.txt",
1514 self.NEWFILE_CONTENTS))
1517 def test_POST_upload_named(self):
1519 d = self.POST(self.public_url + "/foo", t="upload",
1520 name="new.txt", file=self.NEWFILE_CONTENTS)
1521 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1522 d.addCallback(lambda res:
1523 self.failUnlessChildContentsAre(fn, u"new.txt",
1524 self.NEWFILE_CONTENTS))
1527 def test_POST_upload_named_badfilename(self):
1528 d = self.POST(self.public_url + "/foo", t="upload",
1529 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
1530 d.addBoth(self.shouldFail, error.Error,
1531 "test_POST_upload_named_badfilename",
1533 "name= may not contain a slash",
1535 # make sure that nothing was added
1536 d.addCallback(lambda res:
1537 self.failUnlessNodeKeysAre(self._foo_node,
1538 [u"bar.txt", u"blockingfile",
1539 u"empty", u"n\u00fc.txt",
1543 def test_POST_FILEURL_check(self):
1544 bar_url = self.public_url + "/foo/bar.txt"
1545 d = self.POST(bar_url, t="check")
1547 self.failUnless("Healthy!" in res)
1548 d.addCallback(_check)
1549 redir_url = "http://allmydata.org/TARGET"
1550 def _check2(statuscode, target):
1551 self.failUnlessEqual(statuscode, str(http.FOUND))
1552 self.failUnlessEqual(target, redir_url)
1553 d.addCallback(lambda res:
1554 self.shouldRedirect2("test_POST_FILEURL_check",
1558 when_done=redir_url))
1559 d.addCallback(lambda res:
1560 self.POST(bar_url, t="check", return_to=redir_url))
1562 self.failUnless("Healthy!" in res)
1563 self.failUnless("Return to parent directory" in res)
1564 self.failUnless(redir_url in res)
1565 d.addCallback(_check3)
1567 d.addCallback(lambda res:
1568 self.POST(bar_url, t="check", output="JSON"))
1569 def _check_json(res):
1570 data = simplejson.loads(res)
1571 self.failUnless("storage-index" in data)
1572 self.failUnless(data["results"]["healthy"])
1573 d.addCallback(_check_json)
1577 def test_POST_FILEURL_check_and_repair(self):
1578 bar_url = self.public_url + "/foo/bar.txt"
1579 d = self.POST(bar_url, t="check", repair="true")
1581 self.failUnless("Healthy!" in res)
1582 d.addCallback(_check)
1583 redir_url = "http://allmydata.org/TARGET"
1584 def _check2(statuscode, target):
1585 self.failUnlessEqual(statuscode, str(http.FOUND))
1586 self.failUnlessEqual(target, redir_url)
1587 d.addCallback(lambda res:
1588 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
1591 t="check", repair="true",
1592 when_done=redir_url))
1593 d.addCallback(lambda res:
1594 self.POST(bar_url, t="check", return_to=redir_url))
1596 self.failUnless("Healthy!" in res)
1597 self.failUnless("Return to parent directory" in res)
1598 self.failUnless(redir_url in res)
1599 d.addCallback(_check3)
1602 def test_POST_DIRURL_check(self):
1603 foo_url = self.public_url + "/foo/"
1604 d = self.POST(foo_url, t="check")
1606 self.failUnless("Healthy!" in res)
1607 d.addCallback(_check)
1608 redir_url = "http://allmydata.org/TARGET"
1609 def _check2(statuscode, target):
1610 self.failUnlessEqual(statuscode, str(http.FOUND))
1611 self.failUnlessEqual(target, redir_url)
1612 d.addCallback(lambda res:
1613 self.shouldRedirect2("test_POST_DIRURL_check",
1617 when_done=redir_url))
1618 d.addCallback(lambda res:
1619 self.POST(foo_url, t="check", return_to=redir_url))
1621 self.failUnless("Healthy!" in res)
1622 self.failUnless("Return to parent directory" in res)
1623 self.failUnless(redir_url in res)
1624 d.addCallback(_check3)
1626 d.addCallback(lambda res:
1627 self.POST(foo_url, t="check", output="JSON"))
1628 def _check_json(res):
1629 data = simplejson.loads(res)
1630 self.failUnless("storage-index" in data)
1631 self.failUnless(data["results"]["healthy"])
1632 d.addCallback(_check_json)
1636 def test_POST_DIRURL_check_and_repair(self):
1637 foo_url = self.public_url + "/foo/"
1638 d = self.POST(foo_url, t="check", repair="true")
1640 self.failUnless("Healthy!" in res)
1641 d.addCallback(_check)
1642 redir_url = "http://allmydata.org/TARGET"
1643 def _check2(statuscode, target):
1644 self.failUnlessEqual(statuscode, str(http.FOUND))
1645 self.failUnlessEqual(target, redir_url)
1646 d.addCallback(lambda res:
1647 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
1650 t="check", repair="true",
1651 when_done=redir_url))
1652 d.addCallback(lambda res:
1653 self.POST(foo_url, t="check", return_to=redir_url))
1655 self.failUnless("Healthy!" in res)
1656 self.failUnless("Return to parent directory" in res)
1657 self.failUnless(redir_url in res)
1658 d.addCallback(_check3)
1661 def wait_for_operation(self, ignored, ophandle):
1662 url = "/operations/" + ophandle
1663 url += "?t=status&output=JSON"
1666 data = simplejson.loads(res)
1667 if not data["finished"]:
1668 d = self.stall(delay=1.0)
1669 d.addCallback(self.wait_for_operation, ophandle)
1675 def get_operation_results(self, ignored, ophandle, output=None):
1676 url = "/operations/" + ophandle
1679 url += "&output=" + output
1682 if output and output.lower() == "json":
1683 return simplejson.loads(res)
1688 def test_POST_DIRURL_deepcheck_no_ophandle(self):
1689 d = self.shouldFail2(error.Error,
1690 "test_POST_DIRURL_deepcheck_no_ophandle",
1692 "slow operation requires ophandle=",
1693 self.POST, self.public_url, t="start-deep-check")
1696 def test_POST_DIRURL_deepcheck(self):
1697 def _check_redirect(statuscode, target):
1698 self.failUnlessEqual(statuscode, str(http.FOUND))
1699 self.failUnless(target.endswith("/operations/123"))
1700 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
1701 self.POST, self.public_url,
1702 t="start-deep-check", ophandle="123")
1703 d.addCallback(self.wait_for_operation, "123")
1704 def _check_json(data):
1705 self.failUnlessEqual(data["finished"], True)
1706 self.failUnlessEqual(data["count-objects-checked"], 8)
1707 self.failUnlessEqual(data["count-objects-healthy"], 8)
1708 d.addCallback(_check_json)
1709 d.addCallback(self.get_operation_results, "123", "html")
1710 def _check_html(res):
1711 self.failUnless("Objects Checked: <span>8</span>" in res)
1712 self.failUnless("Objects Healthy: <span>8</span>" in res)
1713 d.addCallback(_check_html)
1715 d.addCallback(lambda res:
1716 self.GET("/operations/123/"))
1717 d.addCallback(_check_html) # should be the same as without the slash
1719 d.addCallback(lambda res:
1720 self.shouldFail2(error.Error, "one", "404 Not Found",
1721 "No detailed results for SI bogus",
1722 self.GET, "/operations/123/bogus"))
1724 foo_si = self._foo_node.get_storage_index()
1725 foo_si_s = base32.b2a(foo_si)
1726 d.addCallback(lambda res:
1727 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
1728 def _check_foo_json(res):
1729 data = simplejson.loads(res)
1730 self.failUnlessEqual(data["storage-index"], foo_si_s)
1731 self.failUnless(data["results"]["healthy"])
1732 d.addCallback(_check_foo_json)
1735 def test_POST_DIRURL_deepcheck_and_repair(self):
1736 d = self.POST(self.public_url, t="start-deep-check", repair="true",
1737 ophandle="124", output="json", followRedirect=True)
1738 d.addCallback(self.wait_for_operation, "124")
1739 def _check_json(data):
1740 self.failUnlessEqual(data["finished"], True)
1741 self.failUnlessEqual(data["count-objects-checked"], 8)
1742 self.failUnlessEqual(data["count-objects-healthy-pre-repair"], 8)
1743 self.failUnlessEqual(data["count-objects-unhealthy-pre-repair"], 0)
1744 self.failUnlessEqual(data["count-corrupt-shares-pre-repair"], 0)
1745 self.failUnlessEqual(data["count-repairs-attempted"], 0)
1746 self.failUnlessEqual(data["count-repairs-successful"], 0)
1747 self.failUnlessEqual(data["count-repairs-unsuccessful"], 0)
1748 self.failUnlessEqual(data["count-objects-healthy-post-repair"], 8)
1749 self.failUnlessEqual(data["count-objects-unhealthy-post-repair"], 0)
1750 self.failUnlessEqual(data["count-corrupt-shares-post-repair"], 0)
1751 d.addCallback(_check_json)
1752 d.addCallback(self.get_operation_results, "124", "html")
1753 def _check_html(res):
1754 self.failUnless("Objects Checked: <span>8</span>" in res)
1756 self.failUnless("Objects Healthy (before repair): <span>8</span>" in res)
1757 self.failUnless("Objects Unhealthy (before repair): <span>0</span>" in res)
1758 self.failUnless("Corrupt Shares (before repair): <span>0</span>" in res)
1760 self.failUnless("Repairs Attempted: <span>0</span>" in res)
1761 self.failUnless("Repairs Successful: <span>0</span>" in res)
1762 self.failUnless("Repairs Unsuccessful: <span>0</span>" in res)
1764 self.failUnless("Objects Healthy (after repair): <span>8</span>" in res)
1765 self.failUnless("Objects Unhealthy (after repair): <span>0</span>" in res)
1766 self.failUnless("Corrupt Shares (after repair): <span>0</span>" in res)
1767 d.addCallback(_check_html)
1770 def test_POST_FILEURL_bad_t(self):
1771 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
1772 "POST to file: bad t=bogus",
1773 self.POST, self.public_url + "/foo/bar.txt",
1777 def test_POST_mkdir(self): # return value?
1778 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
1779 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1780 d.addCallback(self.failUnlessNodeKeysAre, [])
1783 def test_POST_mkdir_2(self):
1784 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
1785 d.addCallback(lambda res:
1786 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1787 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1788 d.addCallback(self.failUnlessNodeKeysAre, [])
1791 def test_POST_mkdirs_2(self):
1792 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
1793 d.addCallback(lambda res:
1794 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
1795 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
1796 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
1797 d.addCallback(self.failUnlessNodeKeysAre, [])
1800 def test_POST_mkdir_no_parentdir_noredirect(self):
1801 d = self.POST("/uri?t=mkdir")
1802 def _after_mkdir(res):
1803 uri.NewDirectoryURI.init_from_string(res)
1804 d.addCallback(_after_mkdir)
1807 def test_POST_mkdir_no_parentdir_redirect(self):
1808 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
1809 d.addBoth(self.shouldRedirect, None, statuscode='303')
1810 def _check_target(target):
1811 target = urllib.unquote(target)
1812 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
1813 d.addCallback(_check_target)
1816 def test_POST_noparent_bad(self):
1817 d = self.shouldHTTPError2("POST /uri?t=bogus", 400, "Bad Request",
1818 "/uri accepts only PUT, PUT?t=mkdir, "
1819 "POST?t=upload, and POST?t=mkdir",
1820 self.POST, "/uri?t=bogus")
1823 def test_welcome_page_mkdir_button(self):
1824 # Fetch the welcome page.
1826 def _after_get_welcome_page(res):
1827 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)
1828 mo = MKDIR_BUTTON_RE.search(res)
1829 formaction = mo.group(1)
1831 formaname = mo.group(3)
1832 formavalue = mo.group(4)
1833 return (formaction, formt, formaname, formavalue)
1834 d.addCallback(_after_get_welcome_page)
1835 def _after_parse_form(res):
1836 (formaction, formt, formaname, formavalue) = res
1837 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
1838 d.addCallback(_after_parse_form)
1839 d.addBoth(self.shouldRedirect, None, statuscode='303')
1842 def test_POST_mkdir_replace(self): # return value?
1843 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
1844 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1845 d.addCallback(self.failUnlessNodeKeysAre, [])
1848 def test_POST_mkdir_no_replace_queryarg(self): # return value?
1849 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
1850 d.addBoth(self.shouldFail, error.Error,
1851 "POST_mkdir_no_replace_queryarg",
1853 "There was already a child by that name, and you asked me "
1854 "to not replace it")
1855 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1856 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1859 def test_POST_mkdir_no_replace_field(self): # return value?
1860 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
1862 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
1864 "There was already a child by that name, and you asked me "
1865 "to not replace it")
1866 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1867 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1870 def test_POST_mkdir_whendone_field(self):
1871 d = self.POST(self.public_url + "/foo",
1872 t="mkdir", name="newdir", when_done="/THERE")
1873 d.addBoth(self.shouldRedirect, "/THERE")
1874 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1875 d.addCallback(self.failUnlessNodeKeysAre, [])
1878 def test_POST_mkdir_whendone_queryarg(self):
1879 d = self.POST(self.public_url + "/foo?when_done=/THERE",
1880 t="mkdir", name="newdir")
1881 d.addBoth(self.shouldRedirect, "/THERE")
1882 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1883 d.addCallback(self.failUnlessNodeKeysAre, [])
1886 def test_POST_bad_t(self):
1887 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
1888 "POST to a directory with bad t=BOGUS",
1889 self.POST, self.public_url + "/foo", t="BOGUS")
1892 def test_POST_set_children(self):
1893 contents9, n9, newuri9 = self.makefile(9)
1894 contents10, n10, newuri10 = self.makefile(10)
1895 contents11, n11, newuri11 = self.makefile(11)
1898 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
1901 "ctime": 1002777696.7564139,
1902 "mtime": 1002777696.7564139
1905 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
1908 "ctime": 1002777696.7564139,
1909 "mtime": 1002777696.7564139
1912 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
1915 "ctime": 1002777696.7564139,
1916 "mtime": 1002777696.7564139
1919 }""" % (newuri9, newuri10, newuri11)
1921 url = self.webish_url + self.public_url + "/foo" + "?t=set_children"
1923 d = client.getPage(url, method="POST", postdata=reqbody)
1925 self.failUnlessURIMatchesChild(newuri9, self._foo_node, u"atomic_added_1")
1926 self.failUnlessURIMatchesChild(newuri10, self._foo_node, u"atomic_added_2")
1927 self.failUnlessURIMatchesChild(newuri11, self._foo_node, u"atomic_added_3")
1929 d.addCallback(_then)
1930 d.addErrback(self.dump_error)
1933 def test_POST_put_uri(self):
1934 contents, n, newuri = self.makefile(8)
1935 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
1936 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
1937 d.addCallback(lambda res:
1938 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1942 def test_POST_put_uri_replace(self):
1943 contents, n, newuri = self.makefile(8)
1944 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
1945 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
1946 d.addCallback(lambda res:
1947 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1951 def test_POST_put_uri_no_replace_queryarg(self):
1952 contents, n, newuri = self.makefile(8)
1953 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
1954 name="bar.txt", uri=newuri)
1955 d.addBoth(self.shouldFail, error.Error,
1956 "POST_put_uri_no_replace_queryarg",
1958 "There was already a child by that name, and you asked me "
1959 "to not replace it")
1960 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1961 d.addCallback(self.failUnlessIsBarDotTxt)
1964 def test_POST_put_uri_no_replace_field(self):
1965 contents, n, newuri = self.makefile(8)
1966 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
1967 name="bar.txt", uri=newuri)
1968 d.addBoth(self.shouldFail, error.Error,
1969 "POST_put_uri_no_replace_field",
1971 "There was already a child by that name, and you asked me "
1972 "to not replace it")
1973 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1974 d.addCallback(self.failUnlessIsBarDotTxt)
1977 def test_POST_delete(self):
1978 d = self.POST(self.public_url + "/foo", t="delete", name="bar.txt")
1979 d.addCallback(lambda res: self._foo_node.list())
1980 def _check(children):
1981 self.failIf(u"bar.txt" in children)
1982 d.addCallback(_check)
1985 def test_POST_rename_file(self):
1986 d = self.POST(self.public_url + "/foo", t="rename",
1987 from_name="bar.txt", to_name='wibble.txt')
1988 d.addCallback(lambda res:
1989 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1990 d.addCallback(lambda res:
1991 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
1992 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
1993 d.addCallback(self.failUnlessIsBarDotTxt)
1994 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
1995 d.addCallback(self.failUnlessIsBarJSON)
1998 def test_POST_rename_file_redundant(self):
1999 d = self.POST(self.public_url + "/foo", t="rename",
2000 from_name="bar.txt", to_name='bar.txt')
2001 d.addCallback(lambda res:
2002 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2003 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2004 d.addCallback(self.failUnlessIsBarDotTxt)
2005 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
2006 d.addCallback(self.failUnlessIsBarJSON)
2009 def test_POST_rename_file_replace(self):
2010 # rename a file and replace a directory with it
2011 d = self.POST(self.public_url + "/foo", t="rename",
2012 from_name="bar.txt", to_name='empty')
2013 d.addCallback(lambda res:
2014 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
2015 d.addCallback(lambda res:
2016 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
2017 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
2018 d.addCallback(self.failUnlessIsBarDotTxt)
2019 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2020 d.addCallback(self.failUnlessIsBarJSON)
2023 def test_POST_rename_file_no_replace_queryarg(self):
2024 # rename a file and replace a directory with it
2025 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
2026 from_name="bar.txt", to_name='empty')
2027 d.addBoth(self.shouldFail, error.Error,
2028 "POST_rename_file_no_replace_queryarg",
2030 "There was already a child by that name, and you asked me "
2031 "to not replace it")
2032 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2033 d.addCallback(self.failUnlessIsEmptyJSON)
2036 def test_POST_rename_file_no_replace_field(self):
2037 # rename a file and replace a directory with it
2038 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
2039 from_name="bar.txt", to_name='empty')
2040 d.addBoth(self.shouldFail, error.Error,
2041 "POST_rename_file_no_replace_field",
2043 "There was already a child by that name, and you asked me "
2044 "to not replace it")
2045 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2046 d.addCallback(self.failUnlessIsEmptyJSON)
2049 def failUnlessIsEmptyJSON(self, res):
2050 data = simplejson.loads(res)
2051 self.failUnlessEqual(data[0], "dirnode", data)
2052 self.failUnlessEqual(len(data[1]["children"]), 0)
2054 def test_POST_rename_file_slash_fail(self):
2055 d = self.POST(self.public_url + "/foo", t="rename",
2056 from_name="bar.txt", to_name='kirk/spock.txt')
2057 d.addBoth(self.shouldFail, error.Error,
2058 "test_POST_rename_file_slash_fail",
2060 "to_name= may not contain a slash",
2062 d.addCallback(lambda res:
2063 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2066 def test_POST_rename_dir(self):
2067 d = self.POST(self.public_url, t="rename",
2068 from_name="foo", to_name='plunk')
2069 d.addCallback(lambda res:
2070 self.failIfNodeHasChild(self.public_root, u"foo"))
2071 d.addCallback(lambda res:
2072 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
2073 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
2074 d.addCallback(self.failUnlessIsFooJSON)
2077 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
2078 """ If target is not None then the redirection has to go to target. If
2079 statuscode is not None then the redirection has to be accomplished with
2080 that HTTP status code."""
2081 if not isinstance(res, failure.Failure):
2082 to_where = (target is None) and "somewhere" or ("to " + target)
2083 self.fail("%s: we were expecting to get redirected %s, not get an"
2084 " actual page: %s" % (which, to_where, res))
2085 res.trap(error.PageRedirect)
2086 if statuscode is not None:
2087 self.failUnlessEqual(res.value.status, statuscode,
2088 "%s: not a redirect" % which)
2089 if target is not None:
2090 # the PageRedirect does not seem to capture the uri= query arg
2091 # properly, so we can't check for it.
2092 realtarget = self.webish_url + target
2093 self.failUnlessEqual(res.value.location, realtarget,
2094 "%s: wrong target" % which)
2095 return res.value.location
2097 def test_GET_URI_form(self):
2098 base = "/uri?uri=%s" % self._bar_txt_uri
2099 # this is supposed to give us a redirect to /uri/$URI, plus arguments
2100 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
2102 d.addBoth(self.shouldRedirect, targetbase)
2103 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
2104 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
2105 d.addCallback(lambda res: self.GET(base+"&t=json"))
2106 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
2107 d.addCallback(self.log, "about to get file by uri")
2108 d.addCallback(lambda res: self.GET(base, followRedirect=True))
2109 d.addCallback(self.failUnlessIsBarDotTxt)
2110 d.addCallback(self.log, "got file by uri, about to get dir by uri")
2111 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
2112 followRedirect=True))
2113 d.addCallback(self.failUnlessIsFooJSON)
2114 d.addCallback(self.log, "got dir by uri")
2118 def test_GET_URI_form_bad(self):
2119 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
2120 "400 Bad Request", "GET /uri requires uri=",
2124 def test_GET_rename_form(self):
2125 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
2126 followRedirect=True)
2128 self.failUnless('name="when_done" value="."' in res, res)
2129 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
2130 d.addCallback(_check)
2133 def log(self, res, msg):
2134 #print "MSG: %s RES: %s" % (msg, res)
2138 def test_GET_URI_URL(self):
2139 base = "/uri/%s" % self._bar_txt_uri
2141 d.addCallback(self.failUnlessIsBarDotTxt)
2142 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
2143 d.addCallback(self.failUnlessIsBarDotTxt)
2144 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
2145 d.addCallback(self.failUnlessIsBarDotTxt)
2148 def test_GET_URI_URL_dir(self):
2149 base = "/uri/%s?t=json" % self._foo_uri
2151 d.addCallback(self.failUnlessIsFooJSON)
2154 def test_GET_URI_URL_missing(self):
2155 base = "/uri/%s" % self._bad_file_uri
2157 d.addBoth(self.shouldHTTPError, "test_GET_URI_URL_missing",
2158 http.GONE, response_substring="NotEnoughSharesError")
2159 # TODO: how can we exercise both sides of WebDownloadTarget.fail
2160 # here? we must arrange for a download to fail after target.open()
2161 # has been called, and then inspect the response to see that it is
2162 # shorter than we expected.
2165 def test_PUT_NEWFILEURL_uri(self):
2166 contents, n, new_uri = self.makefile(8)
2167 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
2168 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2169 d.addCallback(lambda res:
2170 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
2174 def test_PUT_NEWFILEURL_uri_replace(self):
2175 contents, n, new_uri = self.makefile(8)
2176 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
2177 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2178 d.addCallback(lambda res:
2179 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
2183 def test_PUT_NEWFILEURL_uri_no_replace(self):
2184 contents, n, new_uri = self.makefile(8)
2185 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
2186 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_uri_no_replace",
2188 "There was already a child by that name, and you asked me "
2189 "to not replace it")
2192 def test_PUT_NEWFILE_URI(self):
2193 file_contents = "New file contents here\n"
2194 d = self.PUT("/uri", file_contents)
2196 self.failUnless(uri in FakeCHKFileNode.all_contents)
2197 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
2199 return self.GET("/uri/%s" % uri)
2200 d.addCallback(_check)
2202 self.failUnlessEqual(res, file_contents)
2203 d.addCallback(_check2)
2206 def test_PUT_NEWFILE_URI_only_PUT(self):
2207 d = self.PUT("/uri?t=bogus", "")
2208 d.addBoth(self.shouldFail, error.Error,
2209 "PUT_NEWFILE_URI_only_PUT",
2211 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
2214 def test_PUT_NEWFILE_URI_mutable(self):
2215 file_contents = "New file contents here\n"
2216 d = self.PUT("/uri?mutable=true", file_contents)
2217 def _check_mutable(uri):
2220 self.failUnless(IMutableFileURI.providedBy(u))
2221 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
2222 n = self.s.create_node_from_uri(uri)
2223 return n.download_best_version()
2224 d.addCallback(_check_mutable)
2225 def _check2_mutable(data):
2226 self.failUnlessEqual(data, file_contents)
2227 d.addCallback(_check2_mutable)
2231 self.failUnless(uri in FakeCHKFileNode.all_contents)
2232 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
2234 return self.GET("/uri/%s" % uri)
2235 d.addCallback(_check)
2237 self.failUnlessEqual(res, file_contents)
2238 d.addCallback(_check2)
2241 def test_PUT_mkdir(self):
2242 d = self.PUT("/uri?t=mkdir", "")
2244 n = self.s.create_node_from_uri(uri.strip())
2245 d2 = self.failUnlessNodeKeysAre(n, [])
2246 d2.addCallback(lambda res:
2247 self.GET("/uri/%s?t=json" % uri))
2249 d.addCallback(_check)
2250 d.addCallback(self.failUnlessIsEmptyJSON)
2253 def test_POST_check(self):
2254 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
2256 # this returns a string form of the results, which are probably
2257 # None since we're using fake filenodes.
2258 # TODO: verify that the check actually happened, by changing
2259 # FakeCHKFileNode to count how many times .check() has been
2262 d.addCallback(_done)
2265 def test_bad_method(self):
2266 url = self.webish_url + self.public_url + "/foo/bar.txt"
2267 d = self.shouldHTTPError2("test_bad_method",
2268 501, "Not Implemented",
2269 "I don't know how to treat a BOGUS request.",
2270 client.getPage, url, method="BOGUS")
2273 def test_short_url(self):
2274 url = self.webish_url + "/uri"
2275 d = self.shouldHTTPError2("test_short_url", 501, "Not Implemented",
2276 "I don't know how to treat a DELETE request.",
2277 client.getPage, url, method="DELETE")
2280 def test_ophandle_bad(self):
2281 url = self.webish_url + "/operations/bogus?t=status"
2282 d = self.shouldHTTPError2("test_ophandle_bad", 404, "404 Not Found",
2283 "unknown/expired handle 'bogus'",
2284 client.getPage, url)
2287 def test_ophandle_cancel(self):
2288 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
2289 followRedirect=True)
2290 d.addCallback(lambda ignored:
2291 self.GET("/operations/128?t=status&output=JSON"))
2293 data = simplejson.loads(res)
2294 self.failUnless("finished" in data, res)
2295 monitor = self.ws.root.child_operations.handles["128"][0]
2296 d = self.POST("/operations/128?t=cancel&output=JSON")
2298 data = simplejson.loads(res)
2299 self.failUnless("finished" in data, res)
2300 # t=cancel causes the handle to be forgotten
2301 self.failUnless(monitor.is_cancelled())
2302 d.addCallback(_check2)
2304 d.addCallback(_check1)
2305 d.addCallback(lambda ignored:
2306 self.shouldHTTPError2("test_ophandle_cancel",
2307 404, "404 Not Found",
2308 "unknown/expired handle '128'",
2310 "/operations/128?t=status&output=JSON"))
2313 def test_ophandle_retainfor(self):
2314 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
2315 followRedirect=True)
2316 d.addCallback(lambda ignored:
2317 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
2319 data = simplejson.loads(res)
2320 self.failUnless("finished" in data, res)
2321 d.addCallback(_check1)
2322 # the retain-for=0 will cause the handle to be expired very soon
2323 d.addCallback(self.stall, 2.0)
2324 d.addCallback(lambda ignored:
2325 self.shouldHTTPError2("test_ophandle_retainfor",
2326 404, "404 Not Found",
2327 "unknown/expired handle '129'",
2329 "/operations/129?t=status&output=JSON"))
2332 def test_ophandle_release_after_complete(self):
2333 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
2334 followRedirect=True)
2335 d.addCallback(self.wait_for_operation, "130")
2336 d.addCallback(lambda ignored:
2337 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
2338 # the release-after-complete=true will cause the handle to be expired
2339 d.addCallback(lambda ignored:
2340 self.shouldHTTPError2("test_ophandle_release_after_complete",
2341 404, "404 Not Found",
2342 "unknown/expired handle '130'",
2344 "/operations/130?t=status&output=JSON"))
2347 def test_incident(self):
2348 d = self.POST("/report_incident", details="eek")
2350 self.failUnless("Thank you for your report!" in res, res)
2351 d.addCallback(_done)
2355 class Util(unittest.TestCase):
2356 def test_abbreviate_time(self):
2357 self.failUnlessEqual(common.abbreviate_time(None), "")
2358 self.failUnlessEqual(common.abbreviate_time(1.234), "1.23s")
2359 self.failUnlessEqual(common.abbreviate_time(0.123), "123ms")
2360 self.failUnlessEqual(common.abbreviate_time(0.00123), "1.2ms")
2361 self.failUnlessEqual(common.abbreviate_time(0.000123), "123us")
2363 def test_abbreviate_rate(self):
2364 self.failUnlessEqual(common.abbreviate_rate(None), "")
2365 self.failUnlessEqual(common.abbreviate_rate(1234000), "1.23MBps")
2366 self.failUnlessEqual(common.abbreviate_rate(12340), "12.3kBps")
2367 self.failUnlessEqual(common.abbreviate_rate(123), "123Bps")
2369 def test_abbreviate_size(self):
2370 self.failUnlessEqual(common.abbreviate_size(None), "")
2371 self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
2372 self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
2373 self.failUnlessEqual(common.abbreviate_size(1230), "1.2kB")
2374 self.failUnlessEqual(common.abbreviate_size(123), "123B")