1 import os.path, re, urllib, time
3 from StringIO import StringIO
5 from twisted.application import service
6 from twisted.trial import unittest
7 from twisted.internet import defer, reactor
8 from twisted.internet.task import Clock
9 from twisted.web import client, error, http, html
10 from twisted.python import failure, log
12 from foolscap.api import fireEventually, flushEventualQueue
14 from nevow.util import escapeToXML
15 from nevow import rend
17 from allmydata import interfaces, uri, webish, dirnode
18 from allmydata.storage.shares import get_share_file
19 from allmydata.storage_client import StorageFarmBroker, StubServer
20 from allmydata.immutable import upload
21 from allmydata.immutable.downloader.status import DownloadStatus
22 from allmydata.dirnode import DirectoryNode
23 from allmydata.nodemaker import NodeMaker
24 from allmydata.unknown import UnknownNode
25 from allmydata.web import status, common
26 from allmydata.scripts.debug import CorruptShareOptions, corrupt_share
27 from allmydata.util import fileutil, base32, hashutil
28 from allmydata.util.consumer import download_to_data
29 from allmydata.util.netstring import split_netstring
30 from allmydata.util.encodingutil import to_str
31 from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \
32 create_chk_filenode, WebErrorMixin, ShouldFailMixin, \
33 make_mutable_file_uri, create_mutable_filenode
34 from allmydata.interfaces import IMutableFileNode, SDMF_VERSION, MDMF_VERSION
35 from allmydata.mutable import servermap, publish, retrieve
36 import allmydata.test.common_util as testutil
37 from allmydata.test.no_network import GridTestMixin
38 from allmydata.test.common_web import HTTPClientGETFactory, \
40 from allmydata.client import Client, SecretHolder
41 from allmydata.introducer import IntroducerNode
43 # create a fake uploader/downloader, and a couple of fake dirnodes, then
44 # create a webserver that works against them
46 timeout = 480 # Most of these take longer than 240 seconds on Francois's arm box.
48 unknown_rwcap = u"lafs://from_the_future_rw_\u263A".encode('utf-8')
49 unknown_rocap = u"ro.lafs://readonly_from_the_future_ro_\u263A".encode('utf-8')
50 unknown_immcap = u"imm.lafs://immutable_from_the_future_imm_\u263A".encode('utf-8')
52 FAVICON_MARKUP = '<link href="/icon.png" rel="shortcut icon" />'
55 class FakeStatsProvider:
57 stats = {'stats': {}, 'counters': {}}
60 class FakeNodeMaker(NodeMaker):
65 'max_segment_size':128*1024 # 1024=KiB
67 def _create_lit(self, cap):
68 return FakeCHKFileNode(cap, self.all_contents)
69 def _create_immutable(self, cap):
70 return FakeCHKFileNode(cap, self.all_contents)
71 def _create_mutable(self, cap):
72 return FakeMutableFileNode(None, None,
73 self.encoding_params, None,
74 self.all_contents).init_from_cap(cap)
75 def create_mutable_file(self, contents="", keysize=None,
76 version=SDMF_VERSION):
77 n = FakeMutableFileNode(None, None, self.encoding_params, None,
79 return n.create(contents, version=version)
81 class FakeUploader(service.Service):
84 helper_connected = False
86 def upload(self, uploadable):
87 d = uploadable.get_size()
88 d.addCallback(lambda size: uploadable.read(size))
91 n = create_chk_filenode(data, self.all_contents)
92 ur = upload.UploadResults(file_size=len(data),
99 uri_extension_data={},
100 uri_extension_hash="fake",
101 verifycapstr="fakevcap")
102 ur.set_uri(n.get_uri())
104 d.addCallback(_got_data)
107 def get_helper_info(self):
108 return (self.helper_furl, self.helper_connected)
112 ds = DownloadStatus("storage_index", 1234)
115 serverA = StubServer(hashutil.tagged_hash("foo", "serverid_a")[:20])
116 serverB = StubServer(hashutil.tagged_hash("foo", "serverid_b")[:20])
117 storage_index = hashutil.storage_index_hash("SI")
118 e0 = ds.add_segment_request(0, now)
120 e0.deliver(now+1, 0, 100, 0.5) # when, start,len, decodetime
121 e1 = ds.add_segment_request(1, now+2)
123 # two outstanding requests
124 e2 = ds.add_segment_request(2, now+4)
125 e3 = ds.add_segment_request(3, now+5)
126 del e2,e3 # hush pyflakes
128 # simulate a segment which gets delivered faster than a system clock tick (ticket #1166)
129 e = ds.add_segment_request(4, now)
131 e.deliver(now, 0, 140, 0.5)
133 e = ds.add_dyhb_request(serverA, now)
134 e.finished([1,2], now+1)
135 e = ds.add_dyhb_request(serverB, now+2) # left unfinished
137 e = ds.add_read_event(0, 120, now)
138 e.update(60, 0.5, 0.1) # bytes, decrypttime, pausetime
140 e = ds.add_read_event(120, 30, now+2) # left unfinished
142 e = ds.add_block_request(serverA, 1, 100, 20, now)
143 e.finished(20, now+1)
144 e = ds.add_block_request(serverB, 1, 120, 30, now+1) # left unfinished
146 # make sure that add_read_event() can come first too
147 ds1 = DownloadStatus(storage_index, 1234)
148 e = ds1.add_read_event(0, 120, now)
149 e.update(60, 0.5, 0.1) # bytes, decrypttime, pausetime
155 _all_upload_status = [upload.UploadStatus()]
156 _all_download_status = [build_one_ds()]
157 _all_mapupdate_statuses = [servermap.UpdateStatus()]
158 _all_publish_statuses = [publish.PublishStatus()]
159 _all_retrieve_statuses = [retrieve.RetrieveStatus()]
161 def list_all_upload_statuses(self):
162 return self._all_upload_status
163 def list_all_download_statuses(self):
164 return self._all_download_status
165 def list_all_mapupdate_statuses(self):
166 return self._all_mapupdate_statuses
167 def list_all_publish_statuses(self):
168 return self._all_publish_statuses
169 def list_all_retrieve_statuses(self):
170 return self._all_retrieve_statuses
171 def list_all_helper_statuses(self):
174 class FakeDisplayableServer(StubServer):
175 def __init__(self, serverid, nickname):
176 StubServer.__init__(self, serverid)
177 self.announcement = {"my-version": "allmydata-tahoe-fake",
178 "service-name": "storage",
179 "nickname": nickname}
180 def is_connected(self):
182 def get_permutation_seed(self):
184 def get_remote_host(self):
186 def get_last_loss_time(self):
188 def get_announcement_time(self):
190 def get_announcement(self):
191 return self.announcement
192 def get_nickname(self):
193 return self.announcement["nickname"]
195 class FakeBucketCounter(object):
197 return {"last-complete-bucket-count": 0}
198 def get_progress(self):
199 return {"estimated-time-per-cycle": 0,
200 "cycle-in-progress": False,
201 "remaining-wait-time": 0}
203 class FakeLeaseChecker(object):
205 self.expiration_enabled = False
207 self.override_lease_duration = None
208 self.sharetypes_to_expire = {}
210 return {"history": None}
211 def get_progress(self):
212 return {"estimated-time-per-cycle": 0,
213 "cycle-in-progress": False,
214 "remaining-wait-time": 0}
216 class FakeStorageServer(service.MultiService):
218 def __init__(self, nodeid, nickname):
219 service.MultiService.__init__(self)
220 self.my_nodeid = nodeid
221 self.nickname = nickname
222 self.bucket_counter = FakeBucketCounter()
223 self.lease_checker = FakeLeaseChecker()
225 return {"storage_server.accepting_immutable_shares": False}
227 class FakeClient(Client):
229 # don't upcall to Client.__init__, since we only want to initialize a
231 service.MultiService.__init__(self)
232 self.all_contents = {}
233 self.nodeid = "fake_nodeid"
234 self.nickname = u"fake_nickname \u263A"
235 self.introducer_furl = "None"
236 self.stats_provider = FakeStatsProvider()
237 self._secret_holder = SecretHolder("lease secret", "convergence secret")
239 self.convergence = "some random string"
240 self.storage_broker = StorageFarmBroker(None, permute_peers=True)
241 # fake knowledge of another server
242 self.storage_broker.test_add_server("other_nodeid",
243 FakeDisplayableServer("other_nodeid", u"other_nickname \u263B"))
244 self.introducer_client = None
245 self.history = FakeHistory()
246 self.uploader = FakeUploader()
247 self.uploader.all_contents = self.all_contents
248 self.uploader.setServiceParent(self)
249 self.blacklist = None
250 self.nodemaker = FakeNodeMaker(None, self._secret_holder, None,
253 self.nodemaker.all_contents = self.all_contents
254 self.mutable_file_default = SDMF_VERSION
255 self.addService(FakeStorageServer(self.nodeid, self.nickname))
257 def startService(self):
258 return service.MultiService.startService(self)
259 def stopService(self):
260 return service.MultiService.stopService(self)
262 MUTABLE_SIZELIMIT = FakeMutableFileNode.MUTABLE_SIZELIMIT
264 class WebMixin(object):
266 self.s = FakeClient()
267 self.s.startService()
268 self.staticdir = self.mktemp()
270 self.ws = webish.WebishServer(self.s, "0", staticdir=self.staticdir,
272 self.ws.setServiceParent(self.s)
273 self.webish_port = self.ws.getPortnum()
274 self.webish_url = self.ws.getURL()
275 assert self.webish_url.endswith("/")
276 self.webish_url = self.webish_url[:-1] # these tests add their own /
278 l = [ self.s.create_dirnode() for x in range(6) ]
279 d = defer.DeferredList(l)
281 self.public_root = res[0][1]
282 assert interfaces.IDirectoryNode.providedBy(self.public_root), res
283 self.public_url = "/uri/" + self.public_root.get_uri()
284 self.private_root = res[1][1]
288 self._foo_uri = foo.get_uri()
289 self._foo_readonly_uri = foo.get_readonly_uri()
290 self._foo_verifycap = foo.get_verify_cap().to_string()
291 # NOTE: we ignore the deferred on all set_uri() calls, because we
292 # know the fake nodes do these synchronously
293 self.public_root.set_uri(u"foo", foo.get_uri(),
294 foo.get_readonly_uri())
296 self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
297 foo.set_uri(u"bar.txt", self._bar_txt_uri, self._bar_txt_uri)
298 self._bar_txt_verifycap = n.get_verify_cap().to_string()
301 # XXX: Do we ever use this?
302 self.BAZ_CONTENTS, n, self._baz_txt_uri, self._baz_txt_readonly_uri = self.makefile_mutable(0)
304 foo.set_uri(u"baz.txt", self._baz_txt_uri, self._baz_txt_readonly_uri)
307 self.QUUX_CONTENTS, n, self._quux_txt_uri, self._quux_txt_readonly_uri = self.makefile_mutable(0, mdmf=True)
308 assert self._quux_txt_uri.startswith("URI:MDMF")
309 foo.set_uri(u"quux.txt", self._quux_txt_uri, self._quux_txt_readonly_uri)
311 foo.set_uri(u"empty", res[3][1].get_uri(),
312 res[3][1].get_readonly_uri())
313 sub_uri = res[4][1].get_uri()
314 self._sub_uri = sub_uri
315 foo.set_uri(u"sub", sub_uri, sub_uri)
316 sub = self.s.create_node_from_uri(sub_uri)
319 _ign, n, blocking_uri = self.makefile(1)
320 foo.set_uri(u"blockingfile", blocking_uri, blocking_uri)
322 # filenode to test for html encoding issues
323 self._htmlname_unicode = u"<&weirdly'named\"file>>>_<iframe />.txt"
324 self._htmlname_raw = self._htmlname_unicode.encode('utf-8')
325 self._htmlname_urlencoded = urllib.quote(self._htmlname_raw, '')
326 self._htmlname_escaped = escapeToXML(self._htmlname_raw)
327 self._htmlname_escaped_attr = html.escape(self._htmlname_raw)
328 self._htmlname_escaped_double = escapeToXML(html.escape(self._htmlname_raw))
329 self.HTMLNAME_CONTENTS, n, self._htmlname_txt_uri = self.makefile(0)
330 foo.set_uri(self._htmlname_unicode, self._htmlname_txt_uri, self._htmlname_txt_uri)
332 unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
333 # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
334 # still think of it as an umlaut
335 foo.set_uri(unicode_filename, self._bar_txt_uri, self._bar_txt_uri)
337 self.SUBBAZ_CONTENTS, n, baz_file = self.makefile(2)
338 self._baz_file_uri = baz_file
339 sub.set_uri(u"baz.txt", baz_file, baz_file)
341 _ign, n, self._bad_file_uri = self.makefile(3)
342 # this uri should not be downloadable
343 del self.s.all_contents[self._bad_file_uri]
346 self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri(),
347 rodir.get_readonly_uri())
348 rodir.set_uri(u"nor", baz_file, baz_file)
354 # public/foo/quux.txt
355 # public/foo/blockingfile
356 # public/foo/<&weirdly'named\"file>>>_<iframe />.txt
359 # public/foo/sub/baz.txt
361 # public/reedownlee/nor
362 self.NEWFILE_CONTENTS = "newfile contents\n"
364 return foo.get_metadata_for(u"bar.txt")
366 def _got_metadata(metadata):
367 self._bar_txt_metadata = metadata
368 d.addCallback(_got_metadata)
371 def get_all_contents(self):
372 return self.s.all_contents
374 def makefile(self, number):
375 contents = "contents of file %s\n" % number
376 n = create_chk_filenode(contents, self.get_all_contents())
377 return contents, n, n.get_uri()
379 def makefile_mutable(self, number, mdmf=False):
380 contents = "contents of mutable file %s\n" % number
381 n = create_mutable_filenode(contents, mdmf, self.s.all_contents)
382 return contents, n, n.get_uri(), n.get_readonly_uri()
385 return self.s.stopService()
387 def failUnlessIsBarDotTxt(self, res):
388 self.failUnlessReallyEqual(res, self.BAR_CONTENTS, res)
390 def failUnlessIsQuuxDotTxt(self, res):
391 self.failUnlessReallyEqual(res, self.QUUX_CONTENTS, res)
393 def failUnlessIsBazDotTxt(self, res):
394 self.failUnlessReallyEqual(res, self.BAZ_CONTENTS, res)
396 def failUnlessIsSubBazDotTxt(self, res):
397 self.failUnlessReallyEqual(res, self.SUBBAZ_CONTENTS, res)
399 def failUnlessIsBarJSON(self, res):
400 data = simplejson.loads(res)
401 self.failUnless(isinstance(data, list))
402 self.failUnlessEqual(data[0], "filenode")
403 self.failUnless(isinstance(data[1], dict))
404 self.failIf(data[1]["mutable"])
405 self.failIfIn("rw_uri", data[1]) # immutable
406 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), self._bar_txt_uri)
407 self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._bar_txt_verifycap)
408 self.failUnlessReallyEqual(data[1]["size"], len(self.BAR_CONTENTS))
410 def failUnlessIsQuuxJSON(self, res, readonly=False):
411 data = simplejson.loads(res)
412 self.failUnless(isinstance(data, list))
413 self.failUnlessEqual(data[0], "filenode")
414 self.failUnless(isinstance(data[1], dict))
416 return self.failUnlessIsQuuxDotTxtMetadata(metadata, readonly)
418 def failUnlessIsQuuxDotTxtMetadata(self, metadata, readonly):
419 self.failUnless(metadata['mutable'])
421 self.failIfIn("rw_uri", metadata)
423 self.failUnlessIn("rw_uri", metadata)
424 self.failUnlessEqual(metadata['rw_uri'], self._quux_txt_uri)
425 self.failUnlessIn("ro_uri", metadata)
426 self.failUnlessEqual(metadata['ro_uri'], self._quux_txt_readonly_uri)
427 self.failUnlessReallyEqual(metadata['size'], len(self.QUUX_CONTENTS))
429 def failUnlessIsFooJSON(self, res):
430 data = simplejson.loads(res)
431 self.failUnless(isinstance(data, list))
432 self.failUnlessEqual(data[0], "dirnode", res)
433 self.failUnless(isinstance(data[1], dict))
434 self.failUnless(data[1]["mutable"])
435 self.failUnlessIn("rw_uri", data[1]) # mutable
436 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), self._foo_uri)
437 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), self._foo_readonly_uri)
438 self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._foo_verifycap)
440 kidnames = sorted([unicode(n) for n in data[1]["children"]])
441 self.failUnlessEqual(kidnames,
442 [self._htmlname_unicode, u"bar.txt", u"baz.txt",
443 u"blockingfile", u"empty", u"n\u00fc.txt", u"quux.txt", u"sub"])
444 kids = dict( [(unicode(name),value)
446 in data[1]["children"].iteritems()] )
447 self.failUnlessEqual(kids[u"sub"][0], "dirnode")
448 self.failUnlessIn("metadata", kids[u"sub"][1])
449 self.failUnlessIn("tahoe", kids[u"sub"][1]["metadata"])
450 tahoe_md = kids[u"sub"][1]["metadata"]["tahoe"]
451 self.failUnlessIn("linkcrtime", tahoe_md)
452 self.failUnlessIn("linkmotime", tahoe_md)
453 self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
454 self.failUnlessReallyEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
455 self.failUnlessReallyEqual(to_str(kids[u"bar.txt"][1]["ro_uri"]), self._bar_txt_uri)
456 self.failUnlessReallyEqual(to_str(kids[u"bar.txt"][1]["verify_uri"]),
457 self._bar_txt_verifycap)
458 self.failUnlessIn("metadata", kids[u"bar.txt"][1])
459 self.failUnlessIn("tahoe", kids[u"bar.txt"][1]["metadata"])
460 self.failUnlessReallyEqual(kids[u"bar.txt"][1]["metadata"]["tahoe"]["linkcrtime"],
461 self._bar_txt_metadata["tahoe"]["linkcrtime"])
462 self.failUnlessReallyEqual(to_str(kids[u"n\u00fc.txt"][1]["ro_uri"]),
464 self.failUnlessIn("quux.txt", kids)
465 self.failUnlessReallyEqual(to_str(kids[u"quux.txt"][1]["rw_uri"]),
467 self.failUnlessReallyEqual(to_str(kids[u"quux.txt"][1]["ro_uri"]),
468 self._quux_txt_readonly_uri)
470 def GET(self, urlpath, followRedirect=False, return_response=False,
472 # if return_response=True, this fires with (data, statuscode,
473 # respheaders) instead of just data.
474 assert not isinstance(urlpath, unicode)
475 url = self.webish_url + urlpath
476 factory = HTTPClientGETFactory(url, method="GET",
477 followRedirect=followRedirect, **kwargs)
478 reactor.connectTCP("localhost", self.webish_port, factory)
481 return (data, factory.status, factory.response_headers)
483 d.addCallback(_got_data)
484 return factory.deferred
486 def HEAD(self, urlpath, return_response=False, **kwargs):
487 # this requires some surgery, because twisted.web.client doesn't want
488 # to give us back the response headers.
489 factory = HTTPClientHEADFactory(urlpath, method="HEAD", **kwargs)
490 reactor.connectTCP("localhost", self.webish_port, factory)
493 return (data, factory.status, factory.response_headers)
495 d.addCallback(_got_data)
496 return factory.deferred
498 def PUT(self, urlpath, data, **kwargs):
499 url = self.webish_url + urlpath
500 return client.getPage(url, method="PUT", postdata=data, **kwargs)
502 def DELETE(self, urlpath):
503 url = self.webish_url + urlpath
504 return client.getPage(url, method="DELETE")
506 def POST(self, urlpath, followRedirect=False, **fields):
507 sepbase = "boogabooga"
511 form.append('Content-Disposition: form-data; name="_charset"')
515 for name, value in fields.iteritems():
516 if isinstance(value, tuple):
517 filename, value = value
518 form.append('Content-Disposition: form-data; name="%s"; '
519 'filename="%s"' % (name, filename.encode("utf-8")))
521 form.append('Content-Disposition: form-data; name="%s"' % name)
523 if isinstance(value, unicode):
524 value = value.encode("utf-8")
527 assert isinstance(value, str)
534 body = "\r\n".join(form) + "\r\n"
535 headers["content-type"] = "multipart/form-data; boundary=%s" % sepbase
536 return self.POST2(urlpath, body, headers, followRedirect)
538 def POST2(self, urlpath, body="", headers={}, followRedirect=False):
539 url = self.webish_url + urlpath
540 return client.getPage(url, method="POST", postdata=body,
541 headers=headers, followRedirect=followRedirect)
543 def shouldFail(self, res, expected_failure, which,
544 substring=None, response_substring=None):
545 if isinstance(res, failure.Failure):
546 res.trap(expected_failure)
548 self.failUnlessIn(substring, str(res), which)
549 if response_substring:
550 self.failUnlessIn(response_substring, res.value.response, which)
552 self.fail("%s was supposed to raise %s, not get '%s'" %
553 (which, expected_failure, res))
555 def shouldFail2(self, expected_failure, which, substring,
557 callable, *args, **kwargs):
558 assert substring is None or isinstance(substring, str)
559 assert response_substring is None or isinstance(response_substring, str)
560 d = defer.maybeDeferred(callable, *args, **kwargs)
562 if isinstance(res, failure.Failure):
563 res.trap(expected_failure)
565 self.failUnlessIn(substring, str(res),
566 "'%s' not in '%s' for test '%s'" % \
567 (substring, str(res), which))
568 if response_substring:
569 self.failUnlessIn(response_substring, res.value.response,
570 "'%s' not in '%s' for test '%s'" % \
571 (response_substring, res.value.response,
574 self.fail("%s was supposed to raise %s, not get '%s'" %
575 (which, expected_failure, res))
579 def should404(self, res, which):
580 if isinstance(res, failure.Failure):
581 res.trap(error.Error)
582 self.failUnlessReallyEqual(res.value.status, "404")
584 self.fail("%s was supposed to Error(404), not get '%s'" %
587 def should302(self, res, which):
588 if isinstance(res, failure.Failure):
589 res.trap(error.Error)
590 self.failUnlessReallyEqual(res.value.status, "302")
592 self.fail("%s was supposed to Error(302), not get '%s'" %
596 class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixin, unittest.TestCase):
597 def test_create(self):
600 def test_welcome(self):
603 self.failUnlessIn('Welcome to Tahoe-LAFS', res)
604 self.failUnlessIn(FAVICON_MARKUP, res)
605 self.failUnlessIn('href="https://tahoe-lafs.org/"', res)
606 res_u = res.decode('utf-8')
607 self.failUnlessIn(u'<th>My nickname:</th> <td class="nickname mine">fake_nickname \u263A</td></tr>', res_u)
608 self.failUnlessIn(u'<div class="nickname">other_nickname \u263B</div>', res_u)
610 self.s.basedir = 'web/test_welcome'
611 fileutil.make_dirs("web/test_welcome")
612 fileutil.make_dirs("web/test_welcome/private")
614 d.addCallback(_check)
617 def test_helper_status(self):
618 d = defer.succeed(None)
620 # set helper furl to None
621 def _set_helper_not_configured2(ign):
622 self.s.uploader.helper_furl = None
624 d.addCallback(_set_helper_not_configured2)
625 d.addCallback(lambda res:
626 self.failUnlessIn('Connected to helper?: <span>not configured</span>', res))
628 # enable helper, not connected
629 def _set_helper_not_connected(ign):
630 self.s.uploader.helper_furl = "pb://someHelper"
631 self.s.uploader.helper_connected = False
633 d.addCallback(_set_helper_not_connected)
634 d.addCallback(lambda res:
635 self.failUnlessIn('Connected to helper?: <span>no</span>', res))
637 # enable helper, connected
638 def _set_helper_connected(ign):
639 self.s.uploader.helper_furl = "pb://someHelper"
640 self.s.uploader.helper_connected = True
642 d.addCallback(_set_helper_connected)
643 d.addCallback(lambda res:
644 self.failUnlessIn('Connected to helper?: <span>yes</span>', res))
647 def test_storage(self):
648 d = self.GET("/storage")
650 self.failUnlessIn('Storage Server Status', res)
651 self.failUnlessIn(FAVICON_MARKUP, res)
652 res_u = res.decode('utf-8')
653 self.failUnlessIn(u'<li>Server Nickname: <span class="nickname mine">fake_nickname \u263A</span></li>', res_u)
654 d.addCallback(_check)
657 def test_status(self):
658 h = self.s.get_history()
659 dl_num = h.list_all_download_statuses()[0].get_counter()
660 ul_num = h.list_all_upload_statuses()[0].get_counter()
661 mu_num = h.list_all_mapupdate_statuses()[0].get_counter()
662 pub_num = h.list_all_publish_statuses()[0].get_counter()
663 ret_num = h.list_all_retrieve_statuses()[0].get_counter()
664 d = self.GET("/status", followRedirect=True)
666 self.failUnlessIn('Upload and Download Status', res)
667 self.failUnlessIn('"down-%d"' % dl_num, res)
668 self.failUnlessIn('"up-%d"' % ul_num, res)
669 self.failUnlessIn('"mapupdate-%d"' % mu_num, res)
670 self.failUnlessIn('"publish-%d"' % pub_num, res)
671 self.failUnlessIn('"retrieve-%d"' % ret_num, res)
672 d.addCallback(_check)
673 d.addCallback(lambda res: self.GET("/status/?t=json"))
674 def _check_json(res):
675 data = simplejson.loads(res)
676 self.failUnless(isinstance(data, dict))
677 #active = data["active"]
678 # TODO: test more. We need a way to fake an active operation
680 d.addCallback(_check_json)
682 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
684 self.failUnlessIn("File Download Status", res)
685 d.addCallback(_check_dl)
686 d.addCallback(lambda res: self.GET("/status/down-%d/event_json" % dl_num))
687 def _check_dl_json(res):
688 data = simplejson.loads(res)
689 self.failUnless(isinstance(data, dict))
690 self.failUnlessIn("read", data)
691 self.failUnlessEqual(data["read"][0]["length"], 120)
692 self.failUnlessEqual(data["segment"][0]["segment_length"], 100)
693 self.failUnlessEqual(data["segment"][2]["segment_number"], 2)
694 self.failUnlessEqual(data["segment"][2]["finish_time"], None)
695 phwr_id = base32.b2a(hashutil.tagged_hash("foo", "serverid_a")[:20])
696 cmpu_id = base32.b2a(hashutil.tagged_hash("foo", "serverid_b")[:20])
697 # serverids[] keys are strings, since that's what JSON does, but
698 # we'd really like them to be ints
699 self.failUnlessEqual(data["serverids"]["0"], "phwrsjte")
700 self.failUnless(data["serverids"].has_key("1"),
701 str(data["serverids"]))
702 self.failUnlessEqual(data["serverids"]["1"], "cmpuvkjm",
703 str(data["serverids"]))
704 self.failUnlessEqual(data["server_info"][phwr_id]["short"],
706 self.failUnlessEqual(data["server_info"][cmpu_id]["short"],
708 self.failUnlessIn("dyhb", data)
709 self.failUnlessIn("misc", data)
710 d.addCallback(_check_dl_json)
711 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
713 self.failUnlessIn("File Upload Status", res)
714 d.addCallback(_check_ul)
715 d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num))
716 def _check_mapupdate(res):
717 self.failUnlessIn("Mutable File Servermap Update Status", res)
718 d.addCallback(_check_mapupdate)
719 d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num))
720 def _check_publish(res):
721 self.failUnlessIn("Mutable File Publish Status", res)
722 d.addCallback(_check_publish)
723 d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num))
724 def _check_retrieve(res):
725 self.failUnlessIn("Mutable File Retrieve Status", res)
726 d.addCallback(_check_retrieve)
730 def test_status_numbers(self):
731 drrm = status.DownloadResultsRendererMixin()
732 self.failUnlessReallyEqual(drrm.render_time(None, None), "")
733 self.failUnlessReallyEqual(drrm.render_time(None, 2.5), "2.50s")
734 self.failUnlessReallyEqual(drrm.render_time(None, 0.25), "250ms")
735 self.failUnlessReallyEqual(drrm.render_time(None, 0.0021), "2.1ms")
736 self.failUnlessReallyEqual(drrm.render_time(None, 0.000123), "123us")
737 self.failUnlessReallyEqual(drrm.render_rate(None, None), "")
738 self.failUnlessReallyEqual(drrm.render_rate(None, 2500000), "2.50MBps")
739 self.failUnlessReallyEqual(drrm.render_rate(None, 30100), "30.1kBps")
740 self.failUnlessReallyEqual(drrm.render_rate(None, 123), "123Bps")
742 urrm = status.UploadResultsRendererMixin()
743 self.failUnlessReallyEqual(urrm.render_time(None, None), "")
744 self.failUnlessReallyEqual(urrm.render_time(None, 2.5), "2.50s")
745 self.failUnlessReallyEqual(urrm.render_time(None, 0.25), "250ms")
746 self.failUnlessReallyEqual(urrm.render_time(None, 0.0021), "2.1ms")
747 self.failUnlessReallyEqual(urrm.render_time(None, 0.000123), "123us")
748 self.failUnlessReallyEqual(urrm.render_rate(None, None), "")
749 self.failUnlessReallyEqual(urrm.render_rate(None, 2500000), "2.50MBps")
750 self.failUnlessReallyEqual(urrm.render_rate(None, 30100), "30.1kBps")
751 self.failUnlessReallyEqual(urrm.render_rate(None, 123), "123Bps")
753 def test_GET_FILEURL(self):
754 d = self.GET(self.public_url + "/foo/bar.txt")
755 d.addCallback(self.failUnlessIsBarDotTxt)
758 def test_GET_FILEURL_range(self):
759 headers = {"range": "bytes=1-10"}
760 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
761 return_response=True)
762 def _got((res, status, headers)):
763 self.failUnlessReallyEqual(int(status), 206)
764 self.failUnless(headers.has_key("content-range"))
765 self.failUnlessReallyEqual(headers["content-range"][0],
766 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
767 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[1:11])
771 def test_GET_FILEURL_partial_range(self):
772 headers = {"range": "bytes=5-"}
773 length = len(self.BAR_CONTENTS)
774 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
775 return_response=True)
776 def _got((res, status, headers)):
777 self.failUnlessReallyEqual(int(status), 206)
778 self.failUnless(headers.has_key("content-range"))
779 self.failUnlessReallyEqual(headers["content-range"][0],
780 "bytes 5-%d/%d" % (length-1, length))
781 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[5:])
785 def test_GET_FILEURL_partial_end_range(self):
786 headers = {"range": "bytes=-5"}
787 length = len(self.BAR_CONTENTS)
788 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
789 return_response=True)
790 def _got((res, status, headers)):
791 self.failUnlessReallyEqual(int(status), 206)
792 self.failUnless(headers.has_key("content-range"))
793 self.failUnlessReallyEqual(headers["content-range"][0],
794 "bytes %d-%d/%d" % (length-5, length-1, length))
795 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[-5:])
799 def test_GET_FILEURL_partial_range_overrun(self):
800 headers = {"range": "bytes=100-200"}
801 d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_overrun",
802 "416 Requested Range not satisfiable",
803 "First beyond end of file",
804 self.GET, self.public_url + "/foo/bar.txt",
808 def test_HEAD_FILEURL_range(self):
809 headers = {"range": "bytes=1-10"}
810 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
811 return_response=True)
812 def _got((res, status, headers)):
813 self.failUnlessReallyEqual(res, "")
814 self.failUnlessReallyEqual(int(status), 206)
815 self.failUnless(headers.has_key("content-range"))
816 self.failUnlessReallyEqual(headers["content-range"][0],
817 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
821 def test_HEAD_FILEURL_partial_range(self):
822 headers = {"range": "bytes=5-"}
823 length = len(self.BAR_CONTENTS)
824 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
825 return_response=True)
826 def _got((res, status, headers)):
827 self.failUnlessReallyEqual(int(status), 206)
828 self.failUnless(headers.has_key("content-range"))
829 self.failUnlessReallyEqual(headers["content-range"][0],
830 "bytes 5-%d/%d" % (length-1, length))
834 def test_HEAD_FILEURL_partial_end_range(self):
835 headers = {"range": "bytes=-5"}
836 length = len(self.BAR_CONTENTS)
837 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
838 return_response=True)
839 def _got((res, status, headers)):
840 self.failUnlessReallyEqual(int(status), 206)
841 self.failUnless(headers.has_key("content-range"))
842 self.failUnlessReallyEqual(headers["content-range"][0],
843 "bytes %d-%d/%d" % (length-5, length-1, length))
847 def test_HEAD_FILEURL_partial_range_overrun(self):
848 headers = {"range": "bytes=100-200"}
849 d = self.shouldFail2(error.Error, "test_HEAD_FILEURL_range_overrun",
850 "416 Requested Range not satisfiable",
852 self.HEAD, self.public_url + "/foo/bar.txt",
856 def test_GET_FILEURL_range_bad(self):
857 headers = {"range": "BOGUS=fizbop-quarnak"}
858 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
859 return_response=True)
860 def _got((res, status, headers)):
861 self.failUnlessReallyEqual(int(status), 200)
862 self.failUnless(not headers.has_key("content-range"))
863 self.failUnlessReallyEqual(res, self.BAR_CONTENTS)
867 def test_HEAD_FILEURL(self):
868 d = self.HEAD(self.public_url + "/foo/bar.txt", return_response=True)
869 def _got((res, status, headers)):
870 self.failUnlessReallyEqual(res, "")
871 self.failUnlessReallyEqual(headers["content-length"][0],
872 str(len(self.BAR_CONTENTS)))
873 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
877 def test_GET_FILEURL_named(self):
878 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
879 base2 = "/named/%s" % urllib.quote(self._bar_txt_uri)
880 d = self.GET(base + "/@@name=/blah.txt")
881 d.addCallback(self.failUnlessIsBarDotTxt)
882 d.addCallback(lambda res: self.GET(base + "/blah.txt"))
883 d.addCallback(self.failUnlessIsBarDotTxt)
884 d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt"))
885 d.addCallback(self.failUnlessIsBarDotTxt)
886 d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt"))
887 d.addCallback(self.failUnlessIsBarDotTxt)
888 save_url = base + "?save=true&filename=blah.txt"
889 d.addCallback(lambda res: self.GET(save_url))
890 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
891 u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
892 u_fn_e = urllib.quote(u_filename.encode("utf-8"))
893 u_url = base + "?save=true&filename=" + u_fn_e
894 d.addCallback(lambda res: self.GET(u_url))
895 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
898 def test_PUT_FILEURL_named_bad(self):
899 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
900 d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad",
902 "/file can only be used with GET or HEAD",
903 self.PUT, base + "/@@name=/blah.txt", "")
907 def test_GET_DIRURL_named_bad(self):
908 base = "/file/%s" % urllib.quote(self._foo_uri)
909 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
912 self.GET, base + "/@@name=/blah.txt")
915 def test_GET_slash_file_bad(self):
916 d = self.shouldFail2(error.Error, "test_GET_slash_file_bad",
918 "/file must be followed by a file-cap and a name",
922 def test_GET_unhandled_URI_named(self):
923 contents, n, newuri = self.makefile(12)
924 verifier_cap = n.get_verify_cap().to_string()
925 base = "/file/%s" % urllib.quote(verifier_cap)
926 # client.create_node_from_uri() can't handle verify-caps
927 d = self.shouldFail2(error.Error, "GET_unhandled_URI_named",
928 "400 Bad Request", "is not a file-cap",
932 def test_GET_unhandled_URI(self):
933 contents, n, newuri = self.makefile(12)
934 verifier_cap = n.get_verify_cap().to_string()
935 base = "/uri/%s" % urllib.quote(verifier_cap)
936 # client.create_node_from_uri() can't handle verify-caps
937 d = self.shouldFail2(error.Error, "test_GET_unhandled_URI",
939 "GET unknown URI type: can only do t=info",
943 def test_GET_FILE_URI(self):
944 base = "/uri/%s" % urllib.quote(self._bar_txt_uri)
946 d.addCallback(self.failUnlessIsBarDotTxt)
949 def test_GET_FILE_URI_mdmf(self):
950 base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
952 d.addCallback(self.failUnlessIsQuuxDotTxt)
955 def test_GET_FILE_URI_mdmf_extensions(self):
956 base = "/uri/%s" % urllib.quote("%s:RANDOMSTUFF" % self._quux_txt_uri)
958 d.addCallback(self.failUnlessIsQuuxDotTxt)
961 def test_GET_FILE_URI_mdmf_readonly(self):
962 base = "/uri/%s" % urllib.quote(self._quux_txt_readonly_uri)
964 d.addCallback(self.failUnlessIsQuuxDotTxt)
967 def test_GET_FILE_URI_badchild(self):
968 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
969 errmsg = "Files have no children, certainly not named 'boguschild'"
970 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
971 "400 Bad Request", errmsg,
975 def test_PUT_FILE_URI_badchild(self):
976 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
977 errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory"
978 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
979 "400 Bad Request", errmsg,
983 def test_PUT_FILE_URI_mdmf(self):
984 base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
985 self._quux_new_contents = "new_contents"
987 d.addCallback(lambda res:
988 self.failUnlessIsQuuxDotTxt(res))
989 d.addCallback(lambda ignored:
990 self.PUT(base, self._quux_new_contents))
991 d.addCallback(lambda ignored:
993 d.addCallback(lambda res:
994 self.failUnlessReallyEqual(res, self._quux_new_contents))
997 def test_PUT_FILE_URI_mdmf_extensions(self):
998 base = "/uri/%s" % urllib.quote("%s:EXTENSIONSTUFF" % self._quux_txt_uri)
999 self._quux_new_contents = "new_contents"
1001 d.addCallback(lambda res: self.failUnlessIsQuuxDotTxt(res))
1002 d.addCallback(lambda ignored: self.PUT(base, self._quux_new_contents))
1003 d.addCallback(lambda ignored: self.GET(base))
1004 d.addCallback(lambda res: self.failUnlessEqual(self._quux_new_contents,
1008 def test_PUT_FILE_URI_mdmf_readonly(self):
1009 # We're not allowed to PUT things to a readonly cap.
1010 base = "/uri/%s" % self._quux_txt_readonly_uri
1012 d.addCallback(lambda res:
1013 self.failUnlessIsQuuxDotTxt(res))
1014 # What should we get here? We get a 500 error now; that's not right.
1015 d.addCallback(lambda ignored:
1016 self.shouldFail2(error.Error, "test_PUT_FILE_URI_mdmf_readonly",
1017 "400 Bad Request", "read-only cap",
1018 self.PUT, base, "new data"))
1021 def test_PUT_FILE_URI_sdmf_readonly(self):
1022 # We're not allowed to put things to a readonly cap.
1023 base = "/uri/%s" % self._baz_txt_readonly_uri
1025 d.addCallback(lambda res:
1026 self.failUnlessIsBazDotTxt(res))
1027 d.addCallback(lambda ignored:
1028 self.shouldFail2(error.Error, "test_PUT_FILE_URI_sdmf_readonly",
1029 "400 Bad Request", "read-only cap",
1030 self.PUT, base, "new_data"))
1033 def test_GET_etags(self):
1035 def _check_etags(uri):
1037 d2 = _get_etag(uri, 'json')
1038 d = defer.DeferredList([d1, d2], consumeErrors=True)
1039 def _check(results):
1040 # All deferred must succeed
1041 self.failUnless(all([r[0] for r in results]))
1042 # the etag for the t=json form should be just like the etag
1043 # fo the default t='' form, but with a 'json' suffix
1044 self.failUnlessEqual(results[0][1] + 'json', results[1][1])
1045 d.addCallback(_check)
1048 def _get_etag(uri, t=''):
1049 targetbase = "/uri/%s?t=%s" % (urllib.quote(uri.strip()), t)
1050 d = self.GET(targetbase, return_response=True, followRedirect=True)
1051 def _just_the_etag(result):
1052 data, response, headers = result
1053 etag = headers['etag'][0]
1054 if uri.startswith('URI:DIR'):
1055 self.failUnless(etag.startswith('DIR:'), etag)
1057 return d.addCallback(_just_the_etag)
1059 # Check that etags work with immutable directories
1060 (newkids, caps) = self._create_immutable_children()
1061 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
1062 simplejson.dumps(newkids))
1063 def _stash_immdir_uri(uri):
1064 self._immdir_uri = uri
1066 d.addCallback(_stash_immdir_uri)
1067 d.addCallback(_check_etags)
1069 # Check that etags work with immutable files
1070 d.addCallback(lambda _: _check_etags(self._bar_txt_uri))
1072 # use the ETag on GET
1073 def _check_match(ign):
1074 uri = "/uri/%s" % self._bar_txt_uri
1075 d = self.GET(uri, return_response=True)
1077 d.addCallback(lambda (data, code, headers):
1079 # do a GET that's supposed to match the ETag
1080 d.addCallback(lambda etag:
1081 self.GET(uri, return_response=True,
1082 headers={"If-None-Match": etag}))
1083 # make sure it short-circuited (304 instead of 200)
1084 d.addCallback(lambda (data, code, headers):
1085 self.failUnlessEqual(int(code), http.NOT_MODIFIED))
1087 d.addCallback(_check_match)
1089 def _no_etag(uri, t):
1090 target = "/uri/%s?t=%s" % (uri, t)
1091 d = self.GET(target, return_response=True, followRedirect=True)
1092 d.addCallback(lambda (data, code, headers):
1093 self.failIf("etag" in headers, target))
1095 def _yes_etag(uri, t):
1096 target = "/uri/%s?t=%s" % (uri, t)
1097 d = self.GET(target, return_response=True, followRedirect=True)
1098 d.addCallback(lambda (data, code, headers):
1099 self.failUnless("etag" in headers, target))
1102 d.addCallback(lambda ign: _yes_etag(self._bar_txt_uri, ""))
1103 d.addCallback(lambda ign: _yes_etag(self._bar_txt_uri, "json"))
1104 d.addCallback(lambda ign: _yes_etag(self._bar_txt_uri, "uri"))
1105 d.addCallback(lambda ign: _yes_etag(self._bar_txt_uri, "readonly-uri"))
1106 d.addCallback(lambda ign: _no_etag(self._bar_txt_uri, "info"))
1108 d.addCallback(lambda ign: _yes_etag(self._immdir_uri, ""))
1109 d.addCallback(lambda ign: _yes_etag(self._immdir_uri, "json"))
1110 d.addCallback(lambda ign: _yes_etag(self._immdir_uri, "uri"))
1111 d.addCallback(lambda ign: _yes_etag(self._immdir_uri, "readonly-uri"))
1112 d.addCallback(lambda ign: _no_etag(self._immdir_uri, "info"))
1113 d.addCallback(lambda ign: _no_etag(self._immdir_uri, "rename-form"))
1117 # TODO: version of this with a Unicode filename
1118 def test_GET_FILEURL_save(self):
1119 d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true",
1120 return_response=True)
1121 def _got((res, statuscode, headers)):
1122 content_disposition = headers["content-disposition"][0]
1123 self.failUnless(content_disposition == 'attachment; filename="bar.txt"', content_disposition)
1124 self.failUnlessIsBarDotTxt(res)
1128 def test_GET_FILEURL_missing(self):
1129 d = self.GET(self.public_url + "/foo/missing")
1130 d.addBoth(self.should404, "test_GET_FILEURL_missing")
1133 def test_GET_FILEURL_info_mdmf(self):
1134 d = self.GET("/uri/%s?t=info" % self._quux_txt_uri)
1136 self.failUnlessIn("mutable file (mdmf)", res)
1137 self.failUnlessIn(self._quux_txt_uri, res)
1138 self.failUnlessIn(self._quux_txt_readonly_uri, res)
1142 def test_GET_FILEURL_info_mdmf_readonly(self):
1143 d = self.GET("/uri/%s?t=info" % self._quux_txt_readonly_uri)
1145 self.failUnlessIn("mutable file (mdmf)", res)
1146 self.failIfIn(self._quux_txt_uri, res)
1147 self.failUnlessIn(self._quux_txt_readonly_uri, res)
1151 def test_GET_FILEURL_info_sdmf(self):
1152 d = self.GET("/uri/%s?t=info" % self._baz_txt_uri)
1154 self.failUnlessIn("mutable file (sdmf)", res)
1155 self.failUnlessIn(self._baz_txt_uri, res)
1159 def test_GET_FILEURL_info_mdmf_extensions(self):
1160 d = self.GET("/uri/%s:STUFF?t=info" % self._quux_txt_uri)
1162 self.failUnlessIn("mutable file (mdmf)", res)
1163 self.failUnlessIn(self._quux_txt_uri, res)
1164 self.failUnlessIn(self._quux_txt_readonly_uri, res)
1168 def test_PUT_overwrite_only_files(self):
1169 # create a directory, put a file in that directory.
1170 contents, n, filecap = self.makefile(8)
1171 d = self.PUT(self.public_url + "/foo/dir?t=mkdir", "")
1172 d.addCallback(lambda res:
1173 self.PUT(self.public_url + "/foo/dir/file1.txt",
1174 self.NEWFILE_CONTENTS))
1175 # try to overwrite the file with replace=only-files
1176 # (this should work)
1177 d.addCallback(lambda res:
1178 self.PUT(self.public_url + "/foo/dir/file1.txt?t=uri&replace=only-files",
1180 d.addCallback(lambda res:
1181 self.shouldFail2(error.Error, "PUT_bad_t", "409 Conflict",
1182 "There was already a child by that name, and you asked me "
1183 "to not replace it",
1184 self.PUT, self.public_url + "/foo/dir?t=uri&replace=only-files",
1188 def test_PUT_NEWFILEURL(self):
1189 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
1190 # TODO: we lose the response code, so we can't check this
1191 #self.failUnlessReallyEqual(responsecode, 201)
1192 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
1193 d.addCallback(lambda res:
1194 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1195 self.NEWFILE_CONTENTS))
1198 def test_PUT_NEWFILEURL_not_mutable(self):
1199 d = self.PUT(self.public_url + "/foo/new.txt?mutable=false",
1200 self.NEWFILE_CONTENTS)
1201 # TODO: we lose the response code, so we can't check this
1202 #self.failUnlessReallyEqual(responsecode, 201)
1203 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
1204 d.addCallback(lambda res:
1205 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1206 self.NEWFILE_CONTENTS))
1209 def test_PUT_NEWFILEURL_unlinked_mdmf(self):
1210 # this should get us a few segments of an MDMF mutable file,
1211 # which we can then test for.
1212 contents = self.NEWFILE_CONTENTS * 300000
1213 d = self.PUT("/uri?format=mdmf",
1215 def _got_filecap(filecap):
1216 self.failUnless(filecap.startswith("URI:MDMF"))
1218 d.addCallback(_got_filecap)
1219 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1220 d.addCallback(lambda json: self.failUnlessIn("MDMF", json))
1223 def test_PUT_NEWFILEURL_unlinked_sdmf(self):
1224 contents = self.NEWFILE_CONTENTS * 300000
1225 d = self.PUT("/uri?format=sdmf",
1227 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1228 d.addCallback(lambda json: self.failUnlessIn("SDMF", json))
1231 def test_PUT_NEWFILEURL_unlinked_bad_format(self):
1232 contents = self.NEWFILE_CONTENTS * 300000
1233 return self.shouldHTTPError("PUT_NEWFILEURL_unlinked_bad_format",
1234 400, "Bad Request", "Unknown format: foo",
1235 self.PUT, "/uri?format=foo",
1238 def test_PUT_NEWFILEURL_range_bad(self):
1239 headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
1240 target = self.public_url + "/foo/new.txt"
1241 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad",
1242 "501 Not Implemented",
1243 "Content-Range in PUT not yet supported",
1244 # (and certainly not for immutable files)
1245 self.PUT, target, self.NEWFILE_CONTENTS[1:11],
1247 d.addCallback(lambda res:
1248 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
1251 def test_PUT_NEWFILEURL_mutable(self):
1252 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
1253 self.NEWFILE_CONTENTS)
1254 # TODO: we lose the response code, so we can't check this
1255 #self.failUnlessReallyEqual(responsecode, 201)
1256 def _check_uri(res):
1257 u = uri.from_string_mutable_filenode(res)
1258 self.failUnless(u.is_mutable())
1259 self.failIf(u.is_readonly())
1261 d.addCallback(_check_uri)
1262 d.addCallback(self.failUnlessURIMatchesRWChild, self._foo_node, u"new.txt")
1263 d.addCallback(lambda res:
1264 self.failUnlessMutableChildContentsAre(self._foo_node,
1266 self.NEWFILE_CONTENTS))
1269 def test_PUT_NEWFILEURL_mutable_toobig(self):
1270 # It is okay to upload large mutable files, so we should be able
1272 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
1273 "b" * (self.s.MUTABLE_SIZELIMIT + 1))
1276 def test_PUT_NEWFILEURL_replace(self):
1277 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
1278 # TODO: we lose the response code, so we can't check this
1279 #self.failUnlessReallyEqual(responsecode, 200)
1280 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
1281 d.addCallback(lambda res:
1282 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1283 self.NEWFILE_CONTENTS))
1286 def test_PUT_NEWFILEURL_bad_t(self):
1287 d = self.shouldFail2(error.Error, "PUT_bad_t", "400 Bad Request",
1288 "PUT to a file: bad t=bogus",
1289 self.PUT, self.public_url + "/foo/bar.txt?t=bogus",
1293 def test_PUT_NEWFILEURL_no_replace(self):
1294 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
1295 self.NEWFILE_CONTENTS)
1296 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
1298 "There was already a child by that name, and you asked me "
1299 "to not replace it")
1302 def test_PUT_NEWFILEURL_mkdirs(self):
1303 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
1305 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"newdir/new.txt")
1306 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
1307 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
1308 d.addCallback(lambda res:
1309 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
1310 self.NEWFILE_CONTENTS))
1313 def test_PUT_NEWFILEURL_blocked(self):
1314 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
1315 self.NEWFILE_CONTENTS)
1316 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
1318 "Unable to create directory 'blockingfile': a file was in the way")
1321 def test_PUT_NEWFILEURL_emptyname(self):
1322 # an empty pathname component (i.e. a double-slash) is disallowed
1323 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_emptyname",
1325 "The webapi does not allow empty pathname components",
1326 self.PUT, self.public_url + "/foo//new.txt", "")
1329 def test_DELETE_FILEURL(self):
1330 d = self.DELETE(self.public_url + "/foo/bar.txt")
1331 d.addCallback(lambda res:
1332 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1335 def test_DELETE_FILEURL_missing(self):
1336 d = self.DELETE(self.public_url + "/foo/missing")
1337 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
1340 def test_DELETE_FILEURL_missing2(self):
1341 d = self.DELETE(self.public_url + "/missing/missing")
1342 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
1345 def failUnlessHasBarDotTxtMetadata(self, res):
1346 data = simplejson.loads(res)
1347 self.failUnless(isinstance(data, list))
1348 self.failUnlessIn("metadata", data[1])
1349 self.failUnlessIn("tahoe", data[1]["metadata"])
1350 self.failUnlessIn("linkcrtime", data[1]["metadata"]["tahoe"])
1351 self.failUnlessIn("linkmotime", data[1]["metadata"]["tahoe"])
1352 self.failUnlessReallyEqual(data[1]["metadata"]["tahoe"]["linkcrtime"],
1353 self._bar_txt_metadata["tahoe"]["linkcrtime"])
1355 def test_GET_FILEURL_json(self):
1356 # twisted.web.http.parse_qs ignores any query args without an '=', so
1357 # I can't do "GET /path?json", I have to do "GET /path/t=json"
1358 # instead. This may make it tricky to emulate the S3 interface
1360 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
1362 self.failUnlessIsBarJSON(data)
1363 self.failUnlessHasBarDotTxtMetadata(data)
1365 d.addCallback(_check1)
1368 def test_GET_FILEURL_json_mutable_type(self):
1369 # The JSON should include format, which says whether the
1370 # file is SDMF or MDMF
1371 d = self.PUT("/uri?format=mdmf",
1372 self.NEWFILE_CONTENTS * 300000)
1373 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1374 def _got_json(json, version):
1375 data = simplejson.loads(json)
1376 assert "filenode" == data[0]
1378 assert isinstance(data, dict)
1380 self.failUnlessIn("format", data)
1381 self.failUnlessEqual(data["format"], version)
1383 d.addCallback(_got_json, "MDMF")
1384 # Now make an SDMF file and check that it is reported correctly.
1385 d.addCallback(lambda ignored:
1386 self.PUT("/uri?format=sdmf",
1387 self.NEWFILE_CONTENTS * 300000))
1388 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1389 d.addCallback(_got_json, "SDMF")
1392 def test_GET_FILEURL_json_mdmf(self):
1393 d = self.GET("/uri/%s?t=json" % urllib.quote(self._quux_txt_uri))
1394 d.addCallback(self.failUnlessIsQuuxJSON)
1397 def test_GET_FILEURL_json_missing(self):
1398 d = self.GET(self.public_url + "/foo/missing?json")
1399 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
1402 def test_GET_FILEURL_uri(self):
1403 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
1405 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1406 d.addCallback(_check)
1407 d.addCallback(lambda res:
1408 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
1410 # for now, for files, uris and readonly-uris are the same
1411 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1412 d.addCallback(_check2)
1415 def test_GET_FILEURL_badtype(self):
1416 d = self.shouldHTTPError("GET t=bogus", 400, "Bad Request",
1419 self.public_url + "/foo/bar.txt?t=bogus")
1422 def test_CSS_FILE(self):
1423 d = self.GET("/tahoe.css", followRedirect=True)
1425 CSS_STYLE=re.compile('toolbar\s{.+text-align:\scenter.+toolbar-item.+display:\sinline',re.DOTALL)
1426 self.failUnless(CSS_STYLE.search(res), res)
1427 d.addCallback(_check)
1430 def test_GET_FILEURL_uri_missing(self):
1431 d = self.GET(self.public_url + "/foo/missing?t=uri")
1432 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
1435 def _check_upload_and_mkdir_forms(self, html):
1436 # We should have a form to create a file, with radio buttons that allow
1437 # the user to toggle whether it is a CHK/LIT (default), SDMF, or MDMF file.
1438 self.failUnlessIn('name="t" value="upload"', html)
1439 self.failUnlessIn('input checked="checked" type="radio" id="upload-chk" value="chk" name="format"', html)
1440 self.failUnlessIn('input type="radio" id="upload-sdmf" value="sdmf" name="format"', html)
1441 self.failUnlessIn('input type="radio" id="upload-mdmf" value="mdmf" name="format"', html)
1443 # We should also have the ability to create a mutable directory, with
1444 # radio buttons that allow the user to toggle whether it is an SDMF (default)
1445 # or MDMF directory.
1446 self.failUnlessIn('name="t" value="mkdir"', html)
1447 self.failUnlessIn('input checked="checked" type="radio" id="mkdir-sdmf" value="sdmf" name="format"', html)
1448 self.failUnlessIn('input type="radio" id="mkdir-mdmf" value="mdmf" name="format"', html)
1450 self.failUnlessIn(FAVICON_MARKUP, html)
1452 def test_GET_DIRECTORY_html(self):
1453 d = self.GET(self.public_url + "/foo", followRedirect=True)
1455 self.failUnlessIn('<div class="toolbar-item"><a href="../../..">Return to Welcome page</a></div>', html)
1456 self._check_upload_and_mkdir_forms(html)
1457 self.failUnlessIn("quux", html)
1458 d.addCallback(_check)
1461 def test_GET_DIRECTORY_html_filenode_encoding(self):
1462 d = self.GET(self.public_url + "/foo", followRedirect=True)
1464 # Check if encoded entries are there
1465 self.failUnlessIn('@@named=/' + self._htmlname_urlencoded + '">'
1466 + self._htmlname_escaped + '</a>', html)
1467 self.failUnlessIn('value="' + self._htmlname_escaped_attr + '"', html)
1468 self.failIfIn(self._htmlname_escaped_double, html)
1469 # Make sure that Nevow escaping actually works by checking for unsafe characters
1470 # and that '&' is escaped.
1472 self.failUnlessIn(entity, self._htmlname_raw)
1473 self.failIfIn(entity, self._htmlname_escaped)
1474 self.failUnlessIn('&', re.sub(r'&(amp|lt|gt|quot|apos);', '', self._htmlname_raw))
1475 self.failIfIn('&', re.sub(r'&(amp|lt|gt|quot|apos);', '', self._htmlname_escaped))
1476 d.addCallback(_check)
1479 def test_GET_root_html(self):
1481 d.addCallback(self._check_upload_and_mkdir_forms)
1484 def test_GET_DIRURL(self):
1485 # the addSlash means we get a redirect here
1486 # from /uri/$URI/foo/ , we need ../../../ to get back to the root
1488 d = self.GET(self.public_url + "/foo", followRedirect=True)
1490 self.failUnlessIn('<a href="%s">Return to Welcome page' % ROOT, res)
1492 # the FILE reference points to a URI, but it should end in bar.txt
1493 bar_url = ("%s/file/%s/@@named=/bar.txt" %
1494 (ROOT, urllib.quote(self._bar_txt_uri)))
1495 get_bar = "".join([r'<td>FILE</td>',
1497 r'<a href="%s">bar.txt</a>' % bar_url,
1499 r'\s+<td align="right">%d</td>' % len(self.BAR_CONTENTS),
1501 self.failUnless(re.search(get_bar, res), res)
1502 for label in ['unlink', 'rename/move']:
1503 for line in res.split("\n"):
1504 # find the line that contains the relevant button for bar.txt
1505 if ("form action" in line and
1506 ('value="%s"' % (label,)) in line and
1507 'value="bar.txt"' in line):
1508 # the form target should use a relative URL
1509 foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
1510 self.failUnlessIn('action="%s"' % foo_url, line)
1511 # and the when_done= should too
1512 #done_url = urllib.quote(???)
1513 #self.failUnlessIn('name="when_done" value="%s"' % done_url, line)
1515 # 'unlink' needs to use POST because it directly has a side effect
1516 if label == 'unlink':
1517 self.failUnlessIn('method="post"', line)
1520 self.fail("unable to find '%s bar.txt' line" % (label,))
1522 # the DIR reference just points to a URI
1523 sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
1524 get_sub = ((r'<td>DIR</td>')
1525 +r'\s+<td><a href="%s">sub</a></td>' % sub_url)
1526 self.failUnless(re.search(get_sub, res), res)
1527 d.addCallback(_check)
1529 # look at a readonly directory
1530 d.addCallback(lambda res:
1531 self.GET(self.public_url + "/reedownlee", followRedirect=True))
1533 self.failUnlessIn("(read-only)", res)
1534 self.failIfIn("Upload a file", res)
1535 d.addCallback(_check2)
1537 # and at a directory that contains a readonly directory
1538 d.addCallback(lambda res:
1539 self.GET(self.public_url, followRedirect=True))
1541 self.failUnless(re.search('<td>DIR-RO</td>'
1542 r'\s+<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a></td>', res), res)
1543 d.addCallback(_check3)
1545 # and an empty directory
1546 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty/"))
1548 self.failUnlessIn("directory is empty", res)
1549 MKDIR_BUTTON_RE=re.compile('<input type="hidden" name="t" value="mkdir" />.*<legend class="freeform-form-label">Create a new directory in this directory</legend>.*<input type="submit" value="Create" />', re.I)
1550 self.failUnless(MKDIR_BUTTON_RE.search(res), res)
1551 d.addCallback(_check4)
1553 # and at a literal directory
1554 tiny_litdir_uri = "URI:DIR2-LIT:gqytunj2onug64tufqzdcosvkjetutcjkq5gw4tvm5vwszdgnz5hgyzufqydulbshj5x2lbm" # contains one child which is itself also LIT
1555 d.addCallback(lambda res:
1556 self.GET("/uri/" + tiny_litdir_uri + "/", followRedirect=True))
1558 self.failUnlessIn('(immutable)', res)
1559 self.failUnless(re.search('<td>FILE</td>'
1560 r'\s+<td><a href="[\.\/]+/file/URI%3ALIT%3Akrugkidfnzsc4/@@named=/short">short</a></td>', res), res)
1561 d.addCallback(_check5)
1564 def test_GET_DIRURL_badtype(self):
1565 d = self.shouldHTTPError("test_GET_DIRURL_badtype",
1569 self.public_url + "/foo?t=bogus")
1572 def test_GET_DIRURL_json(self):
1573 d = self.GET(self.public_url + "/foo?t=json")
1574 d.addCallback(self.failUnlessIsFooJSON)
1577 def test_GET_DIRURL_json_format(self):
1578 d = self.PUT(self.public_url + \
1579 "/foo/sdmf.txt?format=sdmf",
1580 self.NEWFILE_CONTENTS * 300000)
1581 d.addCallback(lambda ignored:
1582 self.PUT(self.public_url + \
1583 "/foo/mdmf.txt?format=mdmf",
1584 self.NEWFILE_CONTENTS * 300000))
1585 # Now we have an MDMF and SDMF file in the directory. If we GET
1586 # its JSON, we should see their encodings.
1587 d.addCallback(lambda ignored:
1588 self.GET(self.public_url + "/foo?t=json"))
1589 def _got_json(json):
1590 data = simplejson.loads(json)
1591 assert data[0] == "dirnode"
1594 kids = data['children']
1596 mdmf_data = kids['mdmf.txt'][1]
1597 self.failUnlessIn("format", mdmf_data)
1598 self.failUnlessEqual(mdmf_data["format"], "MDMF")
1600 sdmf_data = kids['sdmf.txt'][1]
1601 self.failUnlessIn("format", sdmf_data)
1602 self.failUnlessEqual(sdmf_data["format"], "SDMF")
1603 d.addCallback(_got_json)
1607 def test_POST_DIRURL_manifest_no_ophandle(self):
1608 d = self.shouldFail2(error.Error,
1609 "test_POST_DIRURL_manifest_no_ophandle",
1611 "slow operation requires ophandle=",
1612 self.POST, self.public_url, t="start-manifest")
1615 def test_POST_DIRURL_manifest(self):
1616 d = defer.succeed(None)
1617 def getman(ignored, output):
1618 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
1619 followRedirect=True)
1620 d.addCallback(self.wait_for_operation, "125")
1621 d.addCallback(self.get_operation_results, "125", output)
1623 d.addCallback(getman, None)
1624 def _got_html(manifest):
1625 self.failUnlessIn("Manifest of SI=", manifest)
1626 self.failUnlessIn("<td>sub</td>", manifest)
1627 self.failUnlessIn(self._sub_uri, manifest)
1628 self.failUnlessIn("<td>sub/baz.txt</td>", manifest)
1629 self.failUnlessIn(FAVICON_MARKUP, manifest)
1630 d.addCallback(_got_html)
1632 # both t=status and unadorned GET should be identical
1633 d.addCallback(lambda res: self.GET("/operations/125"))
1634 d.addCallback(_got_html)
1636 d.addCallback(getman, "html")
1637 d.addCallback(_got_html)
1638 d.addCallback(getman, "text")
1639 def _got_text(manifest):
1640 self.failUnlessIn("\nsub " + self._sub_uri + "\n", manifest)
1641 self.failUnlessIn("\nsub/baz.txt URI:CHK:", manifest)
1642 d.addCallback(_got_text)
1643 d.addCallback(getman, "JSON")
1645 data = res["manifest"]
1647 for (path_list, cap) in data:
1648 got[tuple(path_list)] = cap
1649 self.failUnlessReallyEqual(to_str(got[(u"sub",)]), self._sub_uri)
1650 self.failUnlessIn((u"sub", u"baz.txt"), got)
1651 self.failUnlessIn("finished", res)
1652 self.failUnlessIn("origin", res)
1653 self.failUnlessIn("storage-index", res)
1654 self.failUnlessIn("verifycaps", res)
1655 self.failUnlessIn("stats", res)
1656 d.addCallback(_got_json)
1659 def test_POST_DIRURL_deepsize_no_ophandle(self):
1660 d = self.shouldFail2(error.Error,
1661 "test_POST_DIRURL_deepsize_no_ophandle",
1663 "slow operation requires ophandle=",
1664 self.POST, self.public_url, t="start-deep-size")
1667 def test_POST_DIRURL_deepsize(self):
1668 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
1669 followRedirect=True)
1670 d.addCallback(self.wait_for_operation, "126")
1671 d.addCallback(self.get_operation_results, "126", "json")
1672 def _got_json(data):
1673 self.failUnlessReallyEqual(data["finished"], True)
1675 self.failUnless(size > 1000)
1676 d.addCallback(_got_json)
1677 d.addCallback(self.get_operation_results, "126", "text")
1679 mo = re.search(r'^size: (\d+)$', res, re.M)
1680 self.failUnless(mo, res)
1681 size = int(mo.group(1))
1682 # with directories, the size varies.
1683 self.failUnless(size > 1000)
1684 d.addCallback(_got_text)
1687 def test_POST_DIRURL_deepstats_no_ophandle(self):
1688 d = self.shouldFail2(error.Error,
1689 "test_POST_DIRURL_deepstats_no_ophandle",
1691 "slow operation requires ophandle=",
1692 self.POST, self.public_url, t="start-deep-stats")
1695 def test_POST_DIRURL_deepstats(self):
1696 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
1697 followRedirect=True)
1698 d.addCallback(self.wait_for_operation, "127")
1699 d.addCallback(self.get_operation_results, "127", "json")
1700 def _got_json(stats):
1701 expected = {"count-immutable-files": 4,
1702 "count-mutable-files": 2,
1703 "count-literal-files": 0,
1705 "count-directories": 3,
1706 "size-immutable-files": 76,
1707 "size-literal-files": 0,
1708 #"size-directories": 1912, # varies
1709 #"largest-directory": 1590,
1710 "largest-directory-children": 8,
1711 "largest-immutable-file": 19,
1713 for k,v in expected.iteritems():
1714 self.failUnlessReallyEqual(stats[k], v,
1715 "stats[%s] was %s, not %s" %
1717 self.failUnlessReallyEqual(stats["size-files-histogram"],
1719 d.addCallback(_got_json)
1722 def test_POST_DIRURL_stream_manifest(self):
1723 d = self.POST(self.public_url + "/foo/?t=stream-manifest")
1725 self.failUnless(res.endswith("\n"))
1726 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
1727 self.failUnlessReallyEqual(len(units), 10)
1728 self.failUnlessEqual(units[-1]["type"], "stats")
1730 self.failUnlessEqual(first["path"], [])
1731 self.failUnlessReallyEqual(to_str(first["cap"]), self._foo_uri)
1732 self.failUnlessEqual(first["type"], "directory")
1733 baz = [u for u in units[:-1] if to_str(u["cap"]) == self._baz_file_uri][0]
1734 self.failUnlessEqual(baz["path"], ["sub", "baz.txt"])
1735 self.failIfEqual(baz["storage-index"], None)
1736 self.failIfEqual(baz["verifycap"], None)
1737 self.failIfEqual(baz["repaircap"], None)
1738 # XXX: Add quux and baz to this test.
1740 d.addCallback(_check)
1743 def test_GET_DIRURL_uri(self):
1744 d = self.GET(self.public_url + "/foo?t=uri")
1746 self.failUnlessReallyEqual(to_str(res), self._foo_uri)
1747 d.addCallback(_check)
1750 def test_GET_DIRURL_readonly_uri(self):
1751 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1753 self.failUnlessReallyEqual(to_str(res), self._foo_readonly_uri)
1754 d.addCallback(_check)
1757 def test_PUT_NEWDIRURL(self):
1758 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1759 d.addCallback(lambda res:
1760 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1761 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1762 d.addCallback(self.failUnlessNodeKeysAre, [])
1765 def test_PUT_NEWDIRURL_mdmf(self):
1766 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&format=mdmf", "")
1767 d.addCallback(lambda res:
1768 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1769 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1770 d.addCallback(lambda node:
1771 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
1774 def test_PUT_NEWDIRURL_sdmf(self):
1775 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&format=sdmf",
1777 d.addCallback(lambda res:
1778 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1779 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1780 d.addCallback(lambda node:
1781 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1784 def test_PUT_NEWDIRURL_bad_format(self):
1785 return self.shouldHTTPError("PUT_NEWDIRURL_bad_format",
1786 400, "Bad Request", "Unknown format: foo",
1787 self.PUT, self.public_url +
1788 "/foo/newdir=?t=mkdir&format=foo", "")
1790 def test_POST_NEWDIRURL(self):
1791 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir", "")
1792 d.addCallback(lambda res:
1793 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1794 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1795 d.addCallback(self.failUnlessNodeKeysAre, [])
1798 def test_POST_NEWDIRURL_mdmf(self):
1799 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&format=mdmf", "")
1800 d.addCallback(lambda res:
1801 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1802 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1803 d.addCallback(lambda node:
1804 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
1807 def test_POST_NEWDIRURL_sdmf(self):
1808 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&format=sdmf", "")
1809 d.addCallback(lambda res:
1810 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1811 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1812 d.addCallback(lambda node:
1813 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1816 def test_POST_NEWDIRURL_bad_format(self):
1817 return self.shouldHTTPError("POST_NEWDIRURL_bad_format",
1818 400, "Bad Request", "Unknown format: foo",
1819 self.POST2, self.public_url + \
1820 "/foo/newdir?t=mkdir&format=foo", "")
1822 def test_POST_NEWDIRURL_emptyname(self):
1823 # an empty pathname component (i.e. a double-slash) is disallowed
1824 d = self.shouldFail2(error.Error, "POST_NEWDIRURL_emptyname",
1826 "The webapi does not allow empty pathname components, i.e. a double slash",
1827 self.POST, self.public_url + "//?t=mkdir")
1830 def _do_POST_NEWDIRURL_initial_children_test(self, version=None):
1831 (newkids, caps) = self._create_initial_children()
1832 query = "/foo/newdir?t=mkdir-with-children"
1833 if version == MDMF_VERSION:
1834 query += "&format=mdmf"
1835 elif version == SDMF_VERSION:
1836 query += "&format=sdmf"
1838 version = SDMF_VERSION # for later
1839 d = self.POST2(self.public_url + query,
1840 simplejson.dumps(newkids))
1842 n = self.s.create_node_from_uri(uri.strip())
1843 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1844 self.failUnlessEqual(n._node.get_version(), version)
1845 d2.addCallback(lambda ign:
1846 self.failUnlessROChildURIIs(n, u"child-imm",
1848 d2.addCallback(lambda ign:
1849 self.failUnlessRWChildURIIs(n, u"child-mutable",
1851 d2.addCallback(lambda ign:
1852 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
1854 d2.addCallback(lambda ign:
1855 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
1856 caps['unknown_rocap']))
1857 d2.addCallback(lambda ign:
1858 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
1859 caps['unknown_rwcap']))
1860 d2.addCallback(lambda ign:
1861 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1862 caps['unknown_immcap']))
1863 d2.addCallback(lambda ign:
1864 self.failUnlessRWChildURIIs(n, u"dirchild",
1866 d2.addCallback(lambda ign:
1867 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1869 d2.addCallback(lambda ign:
1870 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1871 caps['emptydircap']))
1873 d.addCallback(_check)
1874 d.addCallback(lambda res:
1875 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1876 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1877 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1878 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1879 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1882 def test_POST_NEWDIRURL_initial_children(self):
1883 return self._do_POST_NEWDIRURL_initial_children_test()
1885 def test_POST_NEWDIRURL_initial_children_mdmf(self):
1886 return self._do_POST_NEWDIRURL_initial_children_test(MDMF_VERSION)
1888 def test_POST_NEWDIRURL_initial_children_sdmf(self):
1889 return self._do_POST_NEWDIRURL_initial_children_test(SDMF_VERSION)
1891 def test_POST_NEWDIRURL_initial_children_bad_format(self):
1892 (newkids, caps) = self._create_initial_children()
1893 return self.shouldHTTPError("POST_NEWDIRURL_initial_children_bad_format",
1894 400, "Bad Request", "Unknown format: foo",
1895 self.POST2, self.public_url + \
1896 "/foo/newdir?t=mkdir-with-children&format=foo",
1897 simplejson.dumps(newkids))
1899 def test_POST_NEWDIRURL_immutable(self):
1900 (newkids, caps) = self._create_immutable_children()
1901 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
1902 simplejson.dumps(newkids))
1904 n = self.s.create_node_from_uri(uri.strip())
1905 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1906 d2.addCallback(lambda ign:
1907 self.failUnlessROChildURIIs(n, u"child-imm",
1909 d2.addCallback(lambda ign:
1910 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1911 caps['unknown_immcap']))
1912 d2.addCallback(lambda ign:
1913 self.failUnlessROChildURIIs(n, u"dirchild-imm",
1915 d2.addCallback(lambda ign:
1916 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1918 d2.addCallback(lambda ign:
1919 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1920 caps['emptydircap']))
1922 d.addCallback(_check)
1923 d.addCallback(lambda res:
1924 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1925 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1926 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1927 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1928 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1929 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1930 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
1931 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1932 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
1933 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1934 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
1935 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1936 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
1937 d.addErrback(self.explain_web_error)
1940 def test_POST_NEWDIRURL_immutable_bad(self):
1941 (newkids, caps) = self._create_initial_children()
1942 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad",
1944 "needed to be immutable but was not",
1946 self.public_url + "/foo/newdir?t=mkdir-immutable",
1947 simplejson.dumps(newkids))
1950 def test_PUT_NEWDIRURL_exists(self):
1951 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1952 d.addCallback(lambda res:
1953 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1954 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1955 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1958 def test_PUT_NEWDIRURL_blocked(self):
1959 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
1960 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
1962 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
1963 d.addCallback(lambda res:
1964 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1965 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1966 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1969 def test_PUT_NEWDIRURL_mkdirs(self):
1970 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1971 d.addCallback(lambda res:
1972 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1973 d.addCallback(lambda res:
1974 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1975 d.addCallback(lambda res:
1976 self._foo_node.get_child_at_path(u"subdir/newdir"))
1977 d.addCallback(self.failUnlessNodeKeysAre, [])
1980 def test_PUT_NEWDIRURL_mkdirs_mdmf(self):
1981 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&format=mdmf", "")
1982 d.addCallback(lambda ignored:
1983 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1984 d.addCallback(lambda ignored:
1985 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1986 d.addCallback(lambda ignored:
1987 self._foo_node.get_child_at_path(u"subdir"))
1988 def _got_subdir(subdir):
1989 # XXX: What we want?
1990 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
1991 self.failUnlessNodeHasChild(subdir, u"newdir")
1992 return subdir.get_child_at_path(u"newdir")
1993 d.addCallback(_got_subdir)
1994 d.addCallback(lambda newdir:
1995 self.failUnlessEqual(newdir._node.get_version(), MDMF_VERSION))
1998 def test_PUT_NEWDIRURL_mkdirs_sdmf(self):
1999 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&format=sdmf", "")
2000 d.addCallback(lambda ignored:
2001 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
2002 d.addCallback(lambda ignored:
2003 self.failIfNodeHasChild(self._foo_node, u"newdir"))
2004 d.addCallback(lambda ignored:
2005 self._foo_node.get_child_at_path(u"subdir"))
2006 def _got_subdir(subdir):
2007 # XXX: What we want?
2008 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
2009 self.failUnlessNodeHasChild(subdir, u"newdir")
2010 return subdir.get_child_at_path(u"newdir")
2011 d.addCallback(_got_subdir)
2012 d.addCallback(lambda newdir:
2013 self.failUnlessEqual(newdir._node.get_version(), SDMF_VERSION))
2016 def test_PUT_NEWDIRURL_mkdirs_bad_format(self):
2017 return self.shouldHTTPError("PUT_NEWDIRURL_mkdirs_bad_format",
2018 400, "Bad Request", "Unknown format: foo",
2019 self.PUT, self.public_url + \
2020 "/foo/subdir/newdir?t=mkdir&format=foo",
2023 def test_DELETE_DIRURL(self):
2024 d = self.DELETE(self.public_url + "/foo")
2025 d.addCallback(lambda res:
2026 self.failIfNodeHasChild(self.public_root, u"foo"))
2029 def test_DELETE_DIRURL_missing(self):
2030 d = self.DELETE(self.public_url + "/foo/missing")
2031 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
2032 d.addCallback(lambda res:
2033 self.failUnlessNodeHasChild(self.public_root, u"foo"))
2036 def test_DELETE_DIRURL_missing2(self):
2037 d = self.DELETE(self.public_url + "/missing")
2038 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
2041 def dump_root(self):
2043 w = webish.DirnodeWalkerMixin()
2044 def visitor(childpath, childnode, metadata):
2046 d = w.walk(self.public_root, visitor)
2049 def failUnlessNodeKeysAre(self, node, expected_keys):
2050 for k in expected_keys:
2051 assert isinstance(k, unicode)
2053 def _check(children):
2054 self.failUnlessReallyEqual(sorted(children.keys()), sorted(expected_keys))
2055 d.addCallback(_check)
2057 def failUnlessNodeHasChild(self, node, name):
2058 assert isinstance(name, unicode)
2060 def _check(children):
2061 self.failUnlessIn(name, children)
2062 d.addCallback(_check)
2064 def failIfNodeHasChild(self, node, name):
2065 assert isinstance(name, unicode)
2067 def _check(children):
2068 self.failIfIn(name, children)
2069 d.addCallback(_check)
2072 def failUnlessChildContentsAre(self, node, name, expected_contents):
2073 assert isinstance(name, unicode)
2074 d = node.get_child_at_path(name)
2075 d.addCallback(lambda node: download_to_data(node))
2076 def _check(contents):
2077 self.failUnlessReallyEqual(contents, expected_contents)
2078 d.addCallback(_check)
2081 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
2082 assert isinstance(name, unicode)
2083 d = node.get_child_at_path(name)
2084 d.addCallback(lambda node: node.download_best_version())
2085 def _check(contents):
2086 self.failUnlessReallyEqual(contents, expected_contents)
2087 d.addCallback(_check)
2090 def failUnlessRWChildURIIs(self, node, name, expected_uri):
2091 assert isinstance(name, unicode)
2092 d = node.get_child_at_path(name)
2094 self.failUnless(child.is_unknown() or not child.is_readonly())
2095 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
2096 self.failUnlessReallyEqual(child.get_write_uri(), expected_uri.strip())
2097 expected_ro_uri = self._make_readonly(expected_uri)
2099 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
2100 d.addCallback(_check)
2103 def failUnlessROChildURIIs(self, node, name, expected_uri):
2104 assert isinstance(name, unicode)
2105 d = node.get_child_at_path(name)
2107 self.failUnless(child.is_unknown() or child.is_readonly())
2108 self.failUnlessReallyEqual(child.get_write_uri(), None)
2109 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
2110 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_uri.strip())
2111 d.addCallback(_check)
2114 def failUnlessURIMatchesRWChild(self, got_uri, node, name):
2115 assert isinstance(name, unicode)
2116 d = node.get_child_at_path(name)
2118 self.failUnless(child.is_unknown() or not child.is_readonly())
2119 self.failUnlessReallyEqual(child.get_uri(), got_uri.strip())
2120 self.failUnlessReallyEqual(child.get_write_uri(), got_uri.strip())
2121 expected_ro_uri = self._make_readonly(got_uri)
2123 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
2124 d.addCallback(_check)
2127 def failUnlessURIMatchesROChild(self, got_uri, node, name):
2128 assert isinstance(name, unicode)
2129 d = node.get_child_at_path(name)
2131 self.failUnless(child.is_unknown() or child.is_readonly())
2132 self.failUnlessReallyEqual(child.get_write_uri(), None)
2133 self.failUnlessReallyEqual(got_uri.strip(), child.get_uri())
2134 self.failUnlessReallyEqual(got_uri.strip(), child.get_readonly_uri())
2135 d.addCallback(_check)
2138 def failUnlessCHKURIHasContents(self, got_uri, contents):
2139 self.failUnless(self.get_all_contents()[got_uri] == contents)
2141 def test_POST_upload(self):
2142 d = self.POST(self.public_url + "/foo", t="upload",
2143 file=("new.txt", self.NEWFILE_CONTENTS))
2145 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
2146 d.addCallback(lambda res:
2147 self.failUnlessChildContentsAre(fn, u"new.txt",
2148 self.NEWFILE_CONTENTS))
2151 def test_POST_upload_unicode(self):
2152 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
2153 d = self.POST(self.public_url + "/foo", t="upload",
2154 file=(filename, self.NEWFILE_CONTENTS))
2156 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
2157 d.addCallback(lambda res:
2158 self.failUnlessChildContentsAre(fn, filename,
2159 self.NEWFILE_CONTENTS))
2160 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
2161 d.addCallback(lambda res: self.GET(target_url))
2162 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
2163 self.NEWFILE_CONTENTS,
2167 def test_POST_upload_unicode_named(self):
2168 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
2169 d = self.POST(self.public_url + "/foo", t="upload",
2171 file=("overridden", self.NEWFILE_CONTENTS))
2173 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
2174 d.addCallback(lambda res:
2175 self.failUnlessChildContentsAre(fn, filename,
2176 self.NEWFILE_CONTENTS))
2177 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
2178 d.addCallback(lambda res: self.GET(target_url))
2179 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
2180 self.NEWFILE_CONTENTS,
2184 def test_POST_upload_no_link(self):
2185 d = self.POST("/uri", t="upload",
2186 file=("new.txt", self.NEWFILE_CONTENTS))
2187 def _check_upload_results(page):
2188 # this should be a page which describes the results of the upload
2189 # that just finished.
2190 self.failUnlessIn("Upload Results:", page)
2191 self.failUnlessIn("URI:", page)
2192 uri_re = re.compile("URI: <tt><span>(.*)</span>")
2193 mo = uri_re.search(page)
2194 self.failUnless(mo, page)
2195 new_uri = mo.group(1)
2197 d.addCallback(_check_upload_results)
2198 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
2201 def test_POST_upload_no_link_whendone(self):
2202 d = self.POST("/uri", t="upload", when_done="/",
2203 file=("new.txt", self.NEWFILE_CONTENTS))
2204 d.addBoth(self.shouldRedirect, "/")
2207 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
2208 d = defer.maybeDeferred(callable, *args, **kwargs)
2210 if isinstance(res, failure.Failure):
2211 res.trap(error.PageRedirect)
2212 statuscode = res.value.status
2213 target = res.value.location
2214 return checker(statuscode, target)
2215 self.fail("%s: callable was supposed to redirect, not return '%s'"
2220 def test_POST_upload_no_link_whendone_results(self):
2221 def check(statuscode, target):
2222 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2223 self.failUnless(target.startswith(self.webish_url), target)
2224 return client.getPage(target, method="GET")
2225 # We encode "uri" as "%75ri" to exercise a case affected by ticket #1860.
2226 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
2228 self.POST, "/uri", t="upload",
2229 when_done="/%75ri/%(uri)s",
2230 file=("new.txt", self.NEWFILE_CONTENTS))
2231 d.addCallback(lambda res:
2232 self.failUnlessReallyEqual(res, self.NEWFILE_CONTENTS))
2235 def test_POST_upload_no_link_mutable(self):
2236 d = self.POST("/uri", t="upload", mutable="true",
2237 file=("new.txt", self.NEWFILE_CONTENTS))
2238 def _check(filecap):
2239 filecap = filecap.strip()
2240 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
2241 self.filecap = filecap
2242 u = uri.WriteableSSKFileURI.init_from_string(filecap)
2243 self.failUnlessIn(u.get_storage_index(), self.get_all_contents())
2244 n = self.s.create_node_from_uri(filecap)
2245 return n.download_best_version()
2246 d.addCallback(_check)
2248 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2249 return self.GET("/uri/%s" % urllib.quote(self.filecap))
2250 d.addCallback(_check2)
2252 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2253 return self.GET("/file/%s" % urllib.quote(self.filecap))
2254 d.addCallback(_check3)
2256 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2257 d.addCallback(_check4)
2260 def test_POST_upload_no_link_mutable_toobig(self):
2261 # The SDMF size limit is no longer in place, so we should be
2262 # able to upload mutable files that are as large as we want them
2264 d = self.POST("/uri", t="upload", mutable="true",
2265 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2269 def test_POST_upload_format_unlinked(self):
2270 def _check_upload_unlinked(ign, format, uri_prefix):
2271 filename = format + ".txt"
2272 d = self.POST("/uri?t=upload&format=" + format,
2273 file=(filename, self.NEWFILE_CONTENTS * 300000))
2274 def _got_results(results):
2275 if format.upper() in ("SDMF", "MDMF"):
2276 # webapi.rst says this returns a filecap
2279 # for immutable, it returns an "upload results page", and
2280 # the filecap is buried inside
2281 line = [l for l in results.split("\n") if "URI: " in l][0]
2282 mo = re.search(r'<span>([^<]+)</span>', line)
2283 filecap = mo.group(1)
2284 self.failUnless(filecap.startswith(uri_prefix),
2285 (uri_prefix, filecap))
2286 return self.GET("/uri/%s?t=json" % filecap)
2287 d.addCallback(_got_results)
2288 def _got_json(json):
2289 data = simplejson.loads(json)
2291 self.failUnlessIn("format", data)
2292 self.failUnlessEqual(data["format"], format.upper())
2293 d.addCallback(_got_json)
2295 d = defer.succeed(None)
2296 d.addCallback(_check_upload_unlinked, "chk", "URI:CHK")
2297 d.addCallback(_check_upload_unlinked, "CHK", "URI:CHK")
2298 d.addCallback(_check_upload_unlinked, "sdmf", "URI:SSK")
2299 d.addCallback(_check_upload_unlinked, "mdmf", "URI:MDMF")
2302 def test_POST_upload_bad_format_unlinked(self):
2303 return self.shouldHTTPError("POST_upload_bad_format_unlinked",
2304 400, "Bad Request", "Unknown format: foo",
2306 "/uri?t=upload&format=foo",
2307 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2309 def test_POST_upload_format(self):
2310 def _check_upload(ign, format, uri_prefix, fn=None):
2311 filename = format + ".txt"
2312 d = self.POST(self.public_url +
2313 "/foo?t=upload&format=" + format,
2314 file=(filename, self.NEWFILE_CONTENTS * 300000))
2315 def _got_filecap(filecap):
2317 filenameu = unicode(filename)
2318 self.failUnlessURIMatchesRWChild(filecap, fn, filenameu)
2319 self.failUnless(filecap.startswith(uri_prefix))
2320 return self.GET(self.public_url + "/foo/%s?t=json" % filename)
2321 d.addCallback(_got_filecap)
2322 def _got_json(json):
2323 data = simplejson.loads(json)
2325 self.failUnlessIn("format", data)
2326 self.failUnlessEqual(data["format"], format.upper())
2327 d.addCallback(_got_json)
2330 d = defer.succeed(None)
2331 d.addCallback(_check_upload, "chk", "URI:CHK")
2332 d.addCallback(_check_upload, "sdmf", "URI:SSK", self._foo_node)
2333 d.addCallback(_check_upload, "mdmf", "URI:MDMF")
2334 d.addCallback(_check_upload, "MDMF", "URI:MDMF")
2337 def test_POST_upload_bad_format(self):
2338 return self.shouldHTTPError("POST_upload_bad_format",
2339 400, "Bad Request", "Unknown format: foo",
2340 self.POST, self.public_url + \
2341 "/foo?t=upload&format=foo",
2342 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2344 def test_POST_upload_mutable(self):
2345 # this creates a mutable file
2346 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
2347 file=("new.txt", self.NEWFILE_CONTENTS))
2349 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2350 d.addCallback(lambda res:
2351 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2352 self.NEWFILE_CONTENTS))
2353 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2355 self.failUnless(IMutableFileNode.providedBy(newnode))
2356 self.failUnless(newnode.is_mutable())
2357 self.failIf(newnode.is_readonly())
2358 self._mutable_node = newnode
2359 self._mutable_uri = newnode.get_uri()
2362 # now upload it again and make sure that the URI doesn't change
2363 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
2364 d.addCallback(lambda res:
2365 self.POST(self.public_url + "/foo", t="upload",
2367 file=("new.txt", NEWER_CONTENTS)))
2368 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2369 d.addCallback(lambda res:
2370 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2372 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2374 self.failUnless(IMutableFileNode.providedBy(newnode))
2375 self.failUnless(newnode.is_mutable())
2376 self.failIf(newnode.is_readonly())
2377 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2378 d.addCallback(_got2)
2380 # upload a second time, using PUT instead of POST
2381 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
2382 d.addCallback(lambda res:
2383 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
2384 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2385 d.addCallback(lambda res:
2386 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2389 # finally list the directory, since mutable files are displayed
2390 # slightly differently
2392 d.addCallback(lambda res:
2393 self.GET(self.public_url + "/foo/",
2394 followRedirect=True))
2395 def _check_page(res):
2396 # TODO: assert more about the contents
2397 self.failUnlessIn("SSK", res)
2399 d.addCallback(_check_page)
2401 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2403 self.failUnless(IMutableFileNode.providedBy(newnode))
2404 self.failUnless(newnode.is_mutable())
2405 self.failIf(newnode.is_readonly())
2406 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2407 d.addCallback(_got3)
2409 # look at the JSON form of the enclosing directory
2410 d.addCallback(lambda res:
2411 self.GET(self.public_url + "/foo/?t=json",
2412 followRedirect=True))
2413 def _check_page_json(res):
2414 parsed = simplejson.loads(res)
2415 self.failUnlessEqual(parsed[0], "dirnode")
2416 children = dict( [(unicode(name),value)
2418 in parsed[1]["children"].iteritems()] )
2419 self.failUnlessIn(u"new.txt", children)
2420 new_json = children[u"new.txt"]
2421 self.failUnlessEqual(new_json[0], "filenode")
2422 self.failUnless(new_json[1]["mutable"])
2423 self.failUnlessReallyEqual(to_str(new_json[1]["rw_uri"]), self._mutable_uri)
2424 ro_uri = self._mutable_node.get_readonly().to_string()
2425 self.failUnlessReallyEqual(to_str(new_json[1]["ro_uri"]), ro_uri)
2426 d.addCallback(_check_page_json)
2428 # and the JSON form of the file
2429 d.addCallback(lambda res:
2430 self.GET(self.public_url + "/foo/new.txt?t=json"))
2431 def _check_file_json(res):
2432 parsed = simplejson.loads(res)
2433 self.failUnlessEqual(parsed[0], "filenode")
2434 self.failUnless(parsed[1]["mutable"])
2435 self.failUnlessReallyEqual(to_str(parsed[1]["rw_uri"]), self._mutable_uri)
2436 ro_uri = self._mutable_node.get_readonly().to_string()
2437 self.failUnlessReallyEqual(to_str(parsed[1]["ro_uri"]), ro_uri)
2438 d.addCallback(_check_file_json)
2440 # and look at t=uri and t=readonly-uri
2441 d.addCallback(lambda res:
2442 self.GET(self.public_url + "/foo/new.txt?t=uri"))
2443 d.addCallback(lambda res: self.failUnlessReallyEqual(res, self._mutable_uri))
2444 d.addCallback(lambda res:
2445 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
2446 def _check_ro_uri(res):
2447 ro_uri = self._mutable_node.get_readonly().to_string()
2448 self.failUnlessReallyEqual(res, ro_uri)
2449 d.addCallback(_check_ro_uri)
2451 # make sure we can get to it from /uri/URI
2452 d.addCallback(lambda res:
2453 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
2454 d.addCallback(lambda res:
2455 self.failUnlessReallyEqual(res, NEW2_CONTENTS))
2457 # and that HEAD computes the size correctly
2458 d.addCallback(lambda res:
2459 self.HEAD(self.public_url + "/foo/new.txt",
2460 return_response=True))
2461 def _got_headers((res, status, headers)):
2462 self.failUnlessReallyEqual(res, "")
2463 self.failUnlessReallyEqual(headers["content-length"][0],
2464 str(len(NEW2_CONTENTS)))
2465 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
2466 d.addCallback(_got_headers)
2468 # make sure that outdated size limits aren't enforced anymore.
2469 d.addCallback(lambda ignored:
2470 self.POST(self.public_url + "/foo", t="upload",
2473 "b" * (self.s.MUTABLE_SIZELIMIT+1))))
2474 d.addErrback(self.dump_error)
2477 def test_POST_upload_mutable_toobig(self):
2478 # SDMF had a size limti that was removed a while ago. MDMF has
2479 # never had a size limit. Test to make sure that we do not
2480 # encounter errors when trying to upload large mutable files,
2481 # since there should be no coded prohibitions regarding large
2483 d = self.POST(self.public_url + "/foo",
2484 t="upload", mutable="true",
2485 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2488 def dump_error(self, f):
2489 # if the web server returns an error code (like 400 Bad Request),
2490 # web.client.getPage puts the HTTP response body into the .response
2491 # attribute of the exception object that it gives back. It does not
2492 # appear in the Failure's repr(), so the ERROR that trial displays
2493 # will be rather terse and unhelpful. addErrback this method to the
2494 # end of your chain to get more information out of these errors.
2495 if f.check(error.Error):
2496 print "web.error.Error:"
2498 print f.value.response
2501 def test_POST_upload_replace(self):
2502 d = self.POST(self.public_url + "/foo", t="upload",
2503 file=("bar.txt", self.NEWFILE_CONTENTS))
2505 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt")
2506 d.addCallback(lambda res:
2507 self.failUnlessChildContentsAre(fn, u"bar.txt",
2508 self.NEWFILE_CONTENTS))
2511 def test_POST_upload_no_replace_ok(self):
2512 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2513 file=("new.txt", self.NEWFILE_CONTENTS))
2514 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
2515 d.addCallback(lambda res: self.failUnlessReallyEqual(res,
2516 self.NEWFILE_CONTENTS))
2519 def test_POST_upload_no_replace_queryarg(self):
2520 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2521 file=("bar.txt", self.NEWFILE_CONTENTS))
2522 d.addBoth(self.shouldFail, error.Error,
2523 "POST_upload_no_replace_queryarg",
2525 "There was already a child by that name, and you asked me "
2526 "to not replace it")
2527 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2528 d.addCallback(self.failUnlessIsBarDotTxt)
2531 def test_POST_upload_no_replace_field(self):
2532 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
2533 file=("bar.txt", self.NEWFILE_CONTENTS))
2534 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
2536 "There was already a child by that name, and you asked me "
2537 "to not replace it")
2538 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2539 d.addCallback(self.failUnlessIsBarDotTxt)
2542 def test_POST_upload_whendone(self):
2543 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
2544 file=("new.txt", self.NEWFILE_CONTENTS))
2545 d.addBoth(self.shouldRedirect, "/THERE")
2547 d.addCallback(lambda res:
2548 self.failUnlessChildContentsAre(fn, u"new.txt",
2549 self.NEWFILE_CONTENTS))
2552 def test_POST_upload_named(self):
2554 d = self.POST(self.public_url + "/foo", t="upload",
2555 name="new.txt", file=self.NEWFILE_CONTENTS)
2556 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
2557 d.addCallback(lambda res:
2558 self.failUnlessChildContentsAre(fn, u"new.txt",
2559 self.NEWFILE_CONTENTS))
2562 def test_POST_upload_named_badfilename(self):
2563 d = self.POST(self.public_url + "/foo", t="upload",
2564 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
2565 d.addBoth(self.shouldFail, error.Error,
2566 "test_POST_upload_named_badfilename",
2568 "name= may not contain a slash",
2570 # make sure that nothing was added
2571 d.addCallback(lambda res:
2572 self.failUnlessNodeKeysAre(self._foo_node,
2573 [self._htmlname_unicode,
2574 u"bar.txt", u"baz.txt", u"blockingfile",
2575 u"empty", u"n\u00fc.txt", u"quux.txt",
2579 def test_POST_FILEURL_check(self):
2580 bar_url = self.public_url + "/foo/bar.txt"
2581 d = self.POST(bar_url, t="check")
2583 self.failUnlessIn("Healthy :", res)
2584 d.addCallback(_check)
2585 redir_url = "http://allmydata.org/TARGET"
2586 def _check2(statuscode, target):
2587 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2588 self.failUnlessReallyEqual(target, redir_url)
2589 d.addCallback(lambda res:
2590 self.shouldRedirect2("test_POST_FILEURL_check",
2594 when_done=redir_url))
2595 d.addCallback(lambda res:
2596 self.POST(bar_url, t="check", return_to=redir_url))
2598 self.failUnlessIn("Healthy :", res)
2599 self.failUnlessIn("Return to file", res)
2600 self.failUnlessIn(redir_url, res)
2601 d.addCallback(_check3)
2603 d.addCallback(lambda res:
2604 self.POST(bar_url, t="check", output="JSON"))
2605 def _check_json(res):
2606 data = simplejson.loads(res)
2607 self.failUnlessIn("storage-index", data)
2608 self.failUnless(data["results"]["healthy"])
2609 d.addCallback(_check_json)
2613 def test_POST_FILEURL_check_and_repair(self):
2614 bar_url = self.public_url + "/foo/bar.txt"
2615 d = self.POST(bar_url, t="check", repair="true")
2617 self.failUnlessIn("Healthy :", res)
2618 d.addCallback(_check)
2619 redir_url = "http://allmydata.org/TARGET"
2620 def _check2(statuscode, target):
2621 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2622 self.failUnlessReallyEqual(target, redir_url)
2623 d.addCallback(lambda res:
2624 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
2627 t="check", repair="true",
2628 when_done=redir_url))
2629 d.addCallback(lambda res:
2630 self.POST(bar_url, t="check", return_to=redir_url))
2632 self.failUnlessIn("Healthy :", res)
2633 self.failUnlessIn("Return to file", res)
2634 self.failUnlessIn(redir_url, res)
2635 d.addCallback(_check3)
2638 def test_POST_DIRURL_check(self):
2639 foo_url = self.public_url + "/foo/"
2640 d = self.POST(foo_url, t="check")
2642 self.failUnlessIn("Healthy :", res)
2643 d.addCallback(_check)
2644 redir_url = "http://allmydata.org/TARGET"
2645 def _check2(statuscode, target):
2646 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2647 self.failUnlessReallyEqual(target, redir_url)
2648 d.addCallback(lambda res:
2649 self.shouldRedirect2("test_POST_DIRURL_check",
2653 when_done=redir_url))
2654 d.addCallback(lambda res:
2655 self.POST(foo_url, t="check", return_to=redir_url))
2657 self.failUnlessIn("Healthy :", res)
2658 self.failUnlessIn("Return to file/directory", res)
2659 self.failUnlessIn(redir_url, res)
2660 d.addCallback(_check3)
2662 d.addCallback(lambda res:
2663 self.POST(foo_url, t="check", output="JSON"))
2664 def _check_json(res):
2665 data = simplejson.loads(res)
2666 self.failUnlessIn("storage-index", data)
2667 self.failUnless(data["results"]["healthy"])
2668 d.addCallback(_check_json)
2672 def test_POST_DIRURL_check_and_repair(self):
2673 foo_url = self.public_url + "/foo/"
2674 d = self.POST(foo_url, t="check", repair="true")
2676 self.failUnlessIn("Healthy :", res)
2677 d.addCallback(_check)
2678 redir_url = "http://allmydata.org/TARGET"
2679 def _check2(statuscode, target):
2680 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2681 self.failUnlessReallyEqual(target, redir_url)
2682 d.addCallback(lambda res:
2683 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
2686 t="check", repair="true",
2687 when_done=redir_url))
2688 d.addCallback(lambda res:
2689 self.POST(foo_url, t="check", return_to=redir_url))
2691 self.failUnlessIn("Healthy :", res)
2692 self.failUnlessIn("Return to file/directory", res)
2693 self.failUnlessIn(redir_url, res)
2694 d.addCallback(_check3)
2697 def test_POST_FILEURL_mdmf_check(self):
2698 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2699 d = self.POST(quux_url, t="check")
2701 self.failUnlessIn("Healthy", res)
2702 d.addCallback(_check)
2703 quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
2704 d.addCallback(lambda ignored:
2705 self.POST(quux_extension_url, t="check"))
2706 d.addCallback(_check)
2709 def test_POST_FILEURL_mdmf_check_and_repair(self):
2710 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2711 d = self.POST(quux_url, t="check", repair="true")
2713 self.failUnlessIn("Healthy", res)
2714 d.addCallback(_check)
2715 quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
2716 d.addCallback(lambda ignored:
2717 self.POST(quux_extension_url, t="check", repair="true"))
2718 d.addCallback(_check)
2721 def wait_for_operation(self, ignored, ophandle):
2722 url = "/operations/" + ophandle
2723 url += "?t=status&output=JSON"
2726 data = simplejson.loads(res)
2727 if not data["finished"]:
2728 d = self.stall(delay=1.0)
2729 d.addCallback(self.wait_for_operation, ophandle)
2735 def get_operation_results(self, ignored, ophandle, output=None):
2736 url = "/operations/" + ophandle
2739 url += "&output=" + output
2742 if output and output.lower() == "json":
2743 return simplejson.loads(res)
2748 def test_POST_DIRURL_deepcheck_no_ophandle(self):
2749 d = self.shouldFail2(error.Error,
2750 "test_POST_DIRURL_deepcheck_no_ophandle",
2752 "slow operation requires ophandle=",
2753 self.POST, self.public_url, t="start-deep-check")
2756 def test_POST_DIRURL_deepcheck(self):
2757 def _check_redirect(statuscode, target):
2758 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2759 self.failUnless(target.endswith("/operations/123"))
2760 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
2761 self.POST, self.public_url,
2762 t="start-deep-check", ophandle="123")
2763 d.addCallback(self.wait_for_operation, "123")
2764 def _check_json(data):
2765 self.failUnlessReallyEqual(data["finished"], True)
2766 self.failUnlessReallyEqual(data["count-objects-checked"], 11)
2767 self.failUnlessReallyEqual(data["count-objects-healthy"], 11)
2768 d.addCallback(_check_json)
2769 d.addCallback(self.get_operation_results, "123", "html")
2770 def _check_html(res):
2771 self.failUnlessIn("Objects Checked: <span>11</span>", res)
2772 self.failUnlessIn("Objects Healthy: <span>11</span>", res)
2773 self.failUnlessIn(FAVICON_MARKUP, res)
2774 d.addCallback(_check_html)
2776 d.addCallback(lambda res:
2777 self.GET("/operations/123/"))
2778 d.addCallback(_check_html) # should be the same as without the slash
2780 d.addCallback(lambda res:
2781 self.shouldFail2(error.Error, "one", "404 Not Found",
2782 "No detailed results for SI bogus",
2783 self.GET, "/operations/123/bogus"))
2785 foo_si = self._foo_node.get_storage_index()
2786 foo_si_s = base32.b2a(foo_si)
2787 d.addCallback(lambda res:
2788 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
2789 def _check_foo_json(res):
2790 data = simplejson.loads(res)
2791 self.failUnlessEqual(data["storage-index"], foo_si_s)
2792 self.failUnless(data["results"]["healthy"])
2793 d.addCallback(_check_foo_json)
2796 def test_POST_DIRURL_deepcheck_and_repair(self):
2797 d = self.POST(self.public_url, t="start-deep-check", repair="true",
2798 ophandle="124", output="json", followRedirect=True)
2799 d.addCallback(self.wait_for_operation, "124")
2800 def _check_json(data):
2801 self.failUnlessReallyEqual(data["finished"], True)
2802 self.failUnlessReallyEqual(data["count-objects-checked"], 11)
2803 self.failUnlessReallyEqual(data["count-objects-healthy-pre-repair"], 11)
2804 self.failUnlessReallyEqual(data["count-objects-unhealthy-pre-repair"], 0)
2805 self.failUnlessReallyEqual(data["count-corrupt-shares-pre-repair"], 0)
2806 self.failUnlessReallyEqual(data["count-repairs-attempted"], 0)
2807 self.failUnlessReallyEqual(data["count-repairs-successful"], 0)
2808 self.failUnlessReallyEqual(data["count-repairs-unsuccessful"], 0)
2809 self.failUnlessReallyEqual(data["count-objects-healthy-post-repair"], 11)
2810 self.failUnlessReallyEqual(data["count-objects-unhealthy-post-repair"], 0)
2811 self.failUnlessReallyEqual(data["count-corrupt-shares-post-repair"], 0)
2812 d.addCallback(_check_json)
2813 d.addCallback(self.get_operation_results, "124", "html")
2814 def _check_html(res):
2815 self.failUnlessIn("Objects Checked: <span>11</span>", res)
2817 self.failUnlessIn("Objects Healthy (before repair): <span>11</span>", res)
2818 self.failUnlessIn("Objects Unhealthy (before repair): <span>0</span>", res)
2819 self.failUnlessIn("Corrupt Shares (before repair): <span>0</span>", res)
2821 self.failUnlessIn("Repairs Attempted: <span>0</span>", res)
2822 self.failUnlessIn("Repairs Successful: <span>0</span>", res)
2823 self.failUnlessIn("Repairs Unsuccessful: <span>0</span>", res)
2825 self.failUnlessIn("Objects Healthy (after repair): <span>11</span>", res)
2826 self.failUnlessIn("Objects Unhealthy (after repair): <span>0</span>", res)
2827 self.failUnlessIn("Corrupt Shares (after repair): <span>0</span>", res)
2829 self.failUnlessIn(FAVICON_MARKUP, res)
2830 d.addCallback(_check_html)
2833 def test_POST_FILEURL_bad_t(self):
2834 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
2835 "POST to file: bad t=bogus",
2836 self.POST, self.public_url + "/foo/bar.txt",
2840 def test_POST_mkdir(self): # return value?
2841 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
2842 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2843 d.addCallback(self.failUnlessNodeKeysAre, [])
2846 def test_POST_mkdir_mdmf(self):
2847 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&format=mdmf")
2848 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2849 d.addCallback(lambda node:
2850 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2853 def test_POST_mkdir_sdmf(self):
2854 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&format=sdmf")
2855 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2856 d.addCallback(lambda node:
2857 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2860 def test_POST_mkdir_bad_format(self):
2861 return self.shouldHTTPError("POST_mkdir_bad_format",
2862 400, "Bad Request", "Unknown format: foo",
2863 self.POST, self.public_url +
2864 "/foo?t=mkdir&name=newdir&format=foo")
2866 def test_POST_mkdir_initial_children(self):
2867 (newkids, caps) = self._create_initial_children()
2868 d = self.POST2(self.public_url +
2869 "/foo?t=mkdir-with-children&name=newdir",
2870 simplejson.dumps(newkids))
2871 d.addCallback(lambda res:
2872 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2873 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2874 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2875 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2876 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2879 def test_POST_mkdir_initial_children_mdmf(self):
2880 (newkids, caps) = self._create_initial_children()
2881 d = self.POST2(self.public_url +
2882 "/foo?t=mkdir-with-children&name=newdir&format=mdmf",
2883 simplejson.dumps(newkids))
2884 d.addCallback(lambda res:
2885 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2886 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2887 d.addCallback(lambda node:
2888 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2889 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2890 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2895 def test_POST_mkdir_initial_children_sdmf(self):
2896 (newkids, caps) = self._create_initial_children()
2897 d = self.POST2(self.public_url +
2898 "/foo?t=mkdir-with-children&name=newdir&format=sdmf",
2899 simplejson.dumps(newkids))
2900 d.addCallback(lambda res:
2901 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2902 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2903 d.addCallback(lambda node:
2904 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2905 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2906 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2910 def test_POST_mkdir_initial_children_bad_format(self):
2911 (newkids, caps) = self._create_initial_children()
2912 return self.shouldHTTPError("POST_mkdir_initial_children_bad_format",
2913 400, "Bad Request", "Unknown format: foo",
2914 self.POST, self.public_url + \
2915 "/foo?t=mkdir-with-children&name=newdir&format=foo",
2916 simplejson.dumps(newkids))
2918 def test_POST_mkdir_immutable(self):
2919 (newkids, caps) = self._create_immutable_children()
2920 d = self.POST2(self.public_url +
2921 "/foo?t=mkdir-immutable&name=newdir",
2922 simplejson.dumps(newkids))
2923 d.addCallback(lambda res:
2924 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2925 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2926 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2927 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2928 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2929 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2930 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
2931 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2932 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
2933 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2934 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
2935 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2936 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
2939 def test_POST_mkdir_immutable_bad(self):
2940 (newkids, caps) = self._create_initial_children()
2941 d = self.shouldFail2(error.Error, "POST_mkdir_immutable_bad",
2943 "needed to be immutable but was not",
2946 "/foo?t=mkdir-immutable&name=newdir",
2947 simplejson.dumps(newkids))
2950 def test_POST_mkdir_2(self):
2951 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
2952 d.addCallback(lambda res:
2953 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2954 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2955 d.addCallback(self.failUnlessNodeKeysAre, [])
2958 def test_POST_mkdirs_2(self):
2959 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
2960 d.addCallback(lambda res:
2961 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
2962 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
2963 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
2964 d.addCallback(self.failUnlessNodeKeysAre, [])
2967 def test_POST_mkdir_no_parentdir_noredirect(self):
2968 d = self.POST("/uri?t=mkdir")
2969 def _after_mkdir(res):
2970 uri.DirectoryURI.init_from_string(res)
2971 d.addCallback(_after_mkdir)
2974 def test_POST_mkdir_no_parentdir_noredirect_mdmf(self):
2975 d = self.POST("/uri?t=mkdir&format=mdmf")
2976 def _after_mkdir(res):
2977 u = uri.from_string(res)
2978 # Check that this is an MDMF writecap
2979 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
2980 d.addCallback(_after_mkdir)
2983 def test_POST_mkdir_no_parentdir_noredirect_sdmf(self):
2984 d = self.POST("/uri?t=mkdir&format=sdmf")
2985 def _after_mkdir(res):
2986 u = uri.from_string(res)
2987 self.failUnlessIsInstance(u, uri.DirectoryURI)
2988 d.addCallback(_after_mkdir)
2991 def test_POST_mkdir_no_parentdir_noredirect_bad_format(self):
2992 return self.shouldHTTPError("POST_mkdir_no_parentdir_noredirect_bad_format",
2993 400, "Bad Request", "Unknown format: foo",
2994 self.POST, self.public_url +
2995 "/uri?t=mkdir&format=foo")
2997 def test_POST_mkdir_no_parentdir_noredirect2(self):
2998 # make sure form-based arguments (as on the welcome page) still work
2999 d = self.POST("/uri", t="mkdir")
3000 def _after_mkdir(res):
3001 uri.DirectoryURI.init_from_string(res)
3002 d.addCallback(_after_mkdir)
3003 d.addErrback(self.explain_web_error)
3006 def test_POST_mkdir_no_parentdir_redirect(self):
3007 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
3008 d.addBoth(self.shouldRedirect, None, statuscode='303')
3009 def _check_target(target):
3010 target = urllib.unquote(target)
3011 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
3012 d.addCallback(_check_target)
3015 def test_POST_mkdir_no_parentdir_redirect2(self):
3016 d = self.POST("/uri", t="mkdir", redirect_to_result="true")
3017 d.addBoth(self.shouldRedirect, None, statuscode='303')
3018 def _check_target(target):
3019 target = urllib.unquote(target)
3020 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
3021 d.addCallback(_check_target)
3022 d.addErrback(self.explain_web_error)
3025 def _make_readonly(self, u):
3026 ro_uri = uri.from_string(u).get_readonly()
3029 return ro_uri.to_string()
3031 def _create_initial_children(self):
3032 contents, n, filecap1 = self.makefile(12)
3033 md1 = {"metakey1": "metavalue1"}
3034 filecap2 = make_mutable_file_uri()
3035 node3 = self.s.create_node_from_uri(make_mutable_file_uri())
3036 filecap3 = node3.get_readonly_uri()
3037 node4 = self.s.create_node_from_uri(make_mutable_file_uri())
3038 dircap = DirectoryNode(node4, None, None).get_uri()
3039 mdmfcap = make_mutable_file_uri(mdmf=True)
3040 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
3041 emptydircap = "URI:DIR2-LIT:"
3042 newkids = {u"child-imm": ["filenode", {"rw_uri": filecap1,
3043 "ro_uri": self._make_readonly(filecap1),
3044 "metadata": md1, }],
3045 u"child-mutable": ["filenode", {"rw_uri": filecap2,
3046 "ro_uri": self._make_readonly(filecap2)}],
3047 u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}],
3048 u"unknownchild-rw": ["unknown", {"rw_uri": unknown_rwcap,
3049 "ro_uri": unknown_rocap}],
3050 u"unknownchild-ro": ["unknown", {"ro_uri": unknown_rocap}],
3051 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
3052 u"dirchild": ["dirnode", {"rw_uri": dircap,
3053 "ro_uri": self._make_readonly(dircap)}],
3054 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
3055 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
3056 u"child-mutable-mdmf": ["filenode", {"rw_uri": mdmfcap,
3057 "ro_uri": self._make_readonly(mdmfcap)}],
3059 return newkids, {'filecap1': filecap1,
3060 'filecap2': filecap2,
3061 'filecap3': filecap3,
3062 'unknown_rwcap': unknown_rwcap,
3063 'unknown_rocap': unknown_rocap,
3064 'unknown_immcap': unknown_immcap,
3066 'litdircap': litdircap,
3067 'emptydircap': emptydircap,
3070 def _create_immutable_children(self):
3071 contents, n, filecap1 = self.makefile(12)
3072 md1 = {"metakey1": "metavalue1"}
3073 tnode = create_chk_filenode("immutable directory contents\n"*10,
3074 self.get_all_contents())
3075 dnode = DirectoryNode(tnode, None, None)
3076 assert not dnode.is_mutable()
3077 immdircap = dnode.get_uri()
3078 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
3079 emptydircap = "URI:DIR2-LIT:"
3080 newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
3081 "metadata": md1, }],
3082 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
3083 u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
3084 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
3085 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
3087 return newkids, {'filecap1': filecap1,
3088 'unknown_immcap': unknown_immcap,
3089 'immdircap': immdircap,
3090 'litdircap': litdircap,
3091 'emptydircap': emptydircap}
3093 def test_POST_mkdir_no_parentdir_initial_children(self):
3094 (newkids, caps) = self._create_initial_children()
3095 d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
3096 def _after_mkdir(res):
3097 self.failUnless(res.startswith("URI:DIR"), res)
3098 n = self.s.create_node_from_uri(res)
3099 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
3100 d2.addCallback(lambda ign:
3101 self.failUnlessROChildURIIs(n, u"child-imm",
3103 d2.addCallback(lambda ign:
3104 self.failUnlessRWChildURIIs(n, u"child-mutable",
3106 d2.addCallback(lambda ign:
3107 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
3109 d2.addCallback(lambda ign:
3110 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
3111 caps['unknown_rwcap']))
3112 d2.addCallback(lambda ign:
3113 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
3114 caps['unknown_rocap']))
3115 d2.addCallback(lambda ign:
3116 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
3117 caps['unknown_immcap']))
3118 d2.addCallback(lambda ign:
3119 self.failUnlessRWChildURIIs(n, u"dirchild",
3122 d.addCallback(_after_mkdir)
3125 def test_POST_mkdir_no_parentdir_unexpected_children(self):
3126 # the regular /uri?t=mkdir operation is specified to ignore its body.
3127 # Only t=mkdir-with-children pays attention to it.
3128 (newkids, caps) = self._create_initial_children()
3129 d = self.shouldHTTPError("POST_mkdir_no_parentdir_unexpected_children",
3131 "t=mkdir does not accept children=, "
3132 "try t=mkdir-with-children instead",
3133 self.POST2, "/uri?t=mkdir", # without children
3134 simplejson.dumps(newkids))
3137 def test_POST_noparent_bad(self):
3138 d = self.shouldHTTPError("POST_noparent_bad",
3140 "/uri accepts only PUT, PUT?t=mkdir, "
3141 "POST?t=upload, and POST?t=mkdir",
3142 self.POST, "/uri?t=bogus")
3145 def test_POST_mkdir_no_parentdir_immutable(self):
3146 (newkids, caps) = self._create_immutable_children()
3147 d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
3148 def _after_mkdir(res):
3149 self.failUnless(res.startswith("URI:DIR"), res)
3150 n = self.s.create_node_from_uri(res)
3151 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
3152 d2.addCallback(lambda ign:
3153 self.failUnlessROChildURIIs(n, u"child-imm",
3155 d2.addCallback(lambda ign:
3156 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
3157 caps['unknown_immcap']))
3158 d2.addCallback(lambda ign:
3159 self.failUnlessROChildURIIs(n, u"dirchild-imm",
3161 d2.addCallback(lambda ign:
3162 self.failUnlessROChildURIIs(n, u"dirchild-lit",
3164 d2.addCallback(lambda ign:
3165 self.failUnlessROChildURIIs(n, u"dirchild-empty",
3166 caps['emptydircap']))
3168 d.addCallback(_after_mkdir)
3171 def test_POST_mkdir_no_parentdir_immutable_bad(self):
3172 (newkids, caps) = self._create_initial_children()
3173 d = self.shouldFail2(error.Error,
3174 "test_POST_mkdir_no_parentdir_immutable_bad",
3176 "needed to be immutable but was not",
3178 "/uri?t=mkdir-immutable",
3179 simplejson.dumps(newkids))
3182 def test_welcome_page_mkdir_button(self):
3183 # Fetch the welcome page.
3185 def _after_get_welcome_page(res):
3186 MKDIR_BUTTON_RE = re.compile(
3187 '<form action="([^"]*)" method="post".*?'
3188 '<input type="hidden" name="t" value="([^"]*)" />'
3189 '<input type="hidden" name="([^"]*)" value="([^"]*)" />'
3190 '<input type="submit" value="Create a directory" />',
3192 mo = MKDIR_BUTTON_RE.search(res)
3193 formaction = mo.group(1)
3195 formaname = mo.group(3)
3196 formavalue = mo.group(4)
3197 return (formaction, formt, formaname, formavalue)
3198 d.addCallback(_after_get_welcome_page)
3199 def _after_parse_form(res):
3200 (formaction, formt, formaname, formavalue) = res
3201 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
3202 d.addCallback(_after_parse_form)
3203 d.addBoth(self.shouldRedirect, None, statuscode='303')
3206 def test_POST_mkdir_replace(self): # return value?
3207 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
3208 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3209 d.addCallback(self.failUnlessNodeKeysAre, [])
3212 def test_POST_mkdir_no_replace_queryarg(self): # return value?
3213 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
3214 d.addBoth(self.shouldFail, error.Error,
3215 "POST_mkdir_no_replace_queryarg",
3217 "There was already a child by that name, and you asked me "
3218 "to not replace it")
3219 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3220 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3223 def test_POST_mkdir_no_replace_field(self): # return value?
3224 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
3226 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
3228 "There was already a child by that name, and you asked me "
3229 "to not replace it")
3230 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3231 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3234 def test_POST_mkdir_whendone_field(self):
3235 d = self.POST(self.public_url + "/foo",
3236 t="mkdir", name="newdir", when_done="/THERE")
3237 d.addBoth(self.shouldRedirect, "/THERE")
3238 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3239 d.addCallback(self.failUnlessNodeKeysAre, [])
3242 def test_POST_mkdir_whendone_queryarg(self):
3243 d = self.POST(self.public_url + "/foo?when_done=/THERE",
3244 t="mkdir", name="newdir")
3245 d.addBoth(self.shouldRedirect, "/THERE")
3246 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3247 d.addCallback(self.failUnlessNodeKeysAre, [])
3250 def test_POST_bad_t(self):
3251 d = self.shouldFail2(error.Error, "POST_bad_t",
3253 "POST to a directory with bad t=BOGUS",
3254 self.POST, self.public_url + "/foo", t="BOGUS")
3257 def test_POST_set_children(self, command_name="set_children"):
3258 contents9, n9, newuri9 = self.makefile(9)
3259 contents10, n10, newuri10 = self.makefile(10)
3260 contents11, n11, newuri11 = self.makefile(11)
3263 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
3266 "ctime": 1002777696.7564139,
3267 "mtime": 1002777696.7564139
3270 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
3273 "ctime": 1002777696.7564139,
3274 "mtime": 1002777696.7564139
3277 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
3280 "ctime": 1002777696.7564139,
3281 "mtime": 1002777696.7564139
3284 }""" % (newuri9, newuri10, newuri11)
3286 url = self.webish_url + self.public_url + "/foo" + "?t=" + command_name
3288 d = client.getPage(url, method="POST", postdata=reqbody)
3290 self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1")
3291 self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2")
3292 self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3")
3294 d.addCallback(_then)
3295 d.addErrback(self.dump_error)
3298 def test_POST_set_children_with_hyphen(self):
3299 return self.test_POST_set_children(command_name="set-children")
3301 def test_POST_link_uri(self):
3302 contents, n, newuri = self.makefile(8)
3303 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
3304 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
3305 d.addCallback(lambda res:
3306 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3310 def test_POST_link_uri_replace(self):
3311 contents, n, newuri = self.makefile(8)
3312 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
3313 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
3314 d.addCallback(lambda res:
3315 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3319 def test_POST_link_uri_unknown_bad(self):
3320 d = self.POST(self.public_url + "/foo", t="uri", name="future.txt", uri=unknown_rwcap)
3321 d.addBoth(self.shouldFail, error.Error,
3322 "POST_link_uri_unknown_bad",
3324 "unknown cap in a write slot")
3327 def test_POST_link_uri_unknown_ro_good(self):
3328 d = self.POST(self.public_url + "/foo", t="uri", name="future-ro.txt", uri=unknown_rocap)
3329 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-ro.txt")
3332 def test_POST_link_uri_unknown_imm_good(self):
3333 d = self.POST(self.public_url + "/foo", t="uri", name="future-imm.txt", uri=unknown_immcap)
3334 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-imm.txt")
3337 def test_POST_link_uri_no_replace_queryarg(self):
3338 contents, n, newuri = self.makefile(8)
3339 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
3340 name="bar.txt", uri=newuri)
3341 d.addBoth(self.shouldFail, error.Error,
3342 "POST_link_uri_no_replace_queryarg",
3344 "There was already a child by that name, and you asked me "
3345 "to not replace it")
3346 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3347 d.addCallback(self.failUnlessIsBarDotTxt)
3350 def test_POST_link_uri_no_replace_field(self):
3351 contents, n, newuri = self.makefile(8)
3352 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
3353 name="bar.txt", uri=newuri)
3354 d.addBoth(self.shouldFail, error.Error,
3355 "POST_link_uri_no_replace_field",
3357 "There was already a child by that name, and you asked me "
3358 "to not replace it")
3359 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3360 d.addCallback(self.failUnlessIsBarDotTxt)
3363 def test_POST_delete(self, command_name='delete'):
3364 d = self._foo_node.list()
3365 def _check_before(children):
3366 self.failUnlessIn(u"bar.txt", children)
3367 d.addCallback(_check_before)
3368 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t=command_name, name="bar.txt"))
3369 d.addCallback(lambda res: self._foo_node.list())
3370 def _check_after(children):
3371 self.failIfIn(u"bar.txt", children)
3372 d.addCallback(_check_after)
3375 def test_POST_unlink(self):
3376 return self.test_POST_delete(command_name='unlink')
3378 def test_POST_rename_file(self):
3379 d = self.POST(self.public_url + "/foo", t="rename",
3380 from_name="bar.txt", to_name='wibble.txt')
3381 d.addCallback(lambda res:
3382 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3383 d.addCallback(lambda res:
3384 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
3385 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
3386 d.addCallback(self.failUnlessIsBarDotTxt)
3387 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
3388 d.addCallback(self.failUnlessIsBarJSON)
3391 def test_POST_rename_file_redundant(self):
3392 d = self.POST(self.public_url + "/foo", t="rename",
3393 from_name="bar.txt", to_name='bar.txt')
3394 d.addCallback(lambda res:
3395 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3396 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3397 d.addCallback(self.failUnlessIsBarDotTxt)
3398 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3399 d.addCallback(self.failUnlessIsBarJSON)
3402 def test_POST_rename_file_replace(self):
3403 # rename a file and replace a directory with it
3404 d = self.POST(self.public_url + "/foo", t="rename",
3405 from_name="bar.txt", to_name='empty')
3406 d.addCallback(lambda res:
3407 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3408 d.addCallback(lambda res:
3409 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
3410 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
3411 d.addCallback(self.failUnlessIsBarDotTxt)
3412 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3413 d.addCallback(self.failUnlessIsBarJSON)
3416 def test_POST_rename_file_no_replace_queryarg(self):
3417 # rename a file and replace a directory with it
3418 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
3419 from_name="bar.txt", to_name='empty')
3420 d.addBoth(self.shouldFail, error.Error,
3421 "POST_rename_file_no_replace_queryarg",
3423 "There was already a child by that name, and you asked me "
3424 "to not replace it")
3425 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3426 d.addCallback(self.failUnlessIsEmptyJSON)
3429 def test_POST_rename_file_no_replace_field(self):
3430 # rename a file and replace a directory with it
3431 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
3432 from_name="bar.txt", to_name='empty')
3433 d.addBoth(self.shouldFail, error.Error,
3434 "POST_rename_file_no_replace_field",
3436 "There was already a child by that name, and you asked me "
3437 "to not replace it")
3438 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3439 d.addCallback(self.failUnlessIsEmptyJSON)
3442 def failUnlessIsEmptyJSON(self, res):
3443 data = simplejson.loads(res)
3444 self.failUnlessEqual(data[0], "dirnode", data)
3445 self.failUnlessReallyEqual(len(data[1]["children"]), 0)
3447 def test_POST_rename_file_slash_fail(self):
3448 d = self.POST(self.public_url + "/foo", t="rename",
3449 from_name="bar.txt", to_name='kirk/spock.txt')
3450 d.addBoth(self.shouldFail, error.Error,
3451 "test_POST_rename_file_slash_fail",
3453 "to_name= may not contain a slash",
3455 d.addCallback(lambda res:
3456 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3459 def test_POST_rename_dir(self):
3460 d = self.POST(self.public_url, t="rename",
3461 from_name="foo", to_name='plunk')
3462 d.addCallback(lambda res:
3463 self.failIfNodeHasChild(self.public_root, u"foo"))
3464 d.addCallback(lambda res:
3465 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
3466 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
3467 d.addCallback(self.failUnlessIsFooJSON)
3470 def test_POST_move_file(self):
3471 d = self.POST(self.public_url + "/foo", t="move",
3472 from_name="bar.txt", to_dir="sub")
3473 d.addCallback(lambda res:
3474 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3475 d.addCallback(lambda res:
3476 self.failUnlessNodeHasChild(self._sub_node, u"bar.txt"))
3477 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt"))
3478 d.addCallback(self.failUnlessIsBarDotTxt)
3479 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt?t=json"))
3480 d.addCallback(self.failUnlessIsBarJSON)
3483 def test_POST_move_file_new_name(self):
3484 d = self.POST(self.public_url + "/foo", t="move",
3485 from_name="bar.txt", to_name="wibble.txt", to_dir="sub")
3486 d.addCallback(lambda res:
3487 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3488 d.addCallback(lambda res:
3489 self.failIfNodeHasChild(self._sub_node, u"bar.txt"))
3490 d.addCallback(lambda res:
3491 self.failUnlessNodeHasChild(self._sub_node, u"wibble.txt"))
3492 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/wibble.txt"))
3493 d.addCallback(self.failUnlessIsBarDotTxt)
3494 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/wibble.txt?t=json"))
3495 d.addCallback(self.failUnlessIsBarJSON)
3498 def test_POST_move_file_replace(self):
3499 d = self.POST(self.public_url + "/foo", t="move",
3500 from_name="bar.txt", to_name="baz.txt", to_dir="sub")
3501 d.addCallback(lambda res:
3502 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3503 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt"))
3504 d.addCallback(self.failUnlessIsBarDotTxt)
3505 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt?t=json"))
3506 d.addCallback(self.failUnlessIsBarJSON)
3509 def test_POST_move_file_no_replace(self):
3510 d = self.shouldFail2(error.Error, "POST_move_file_no_replace",
3512 "There was already a child by that name, and you asked me to not replace it",
3513 self.POST, self.public_url + "/foo", t="move",
3514 replace="false", from_name="bar.txt",
3515 to_name="baz.txt", to_dir="sub")
3516 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3517 d.addCallback(self.failUnlessIsBarDotTxt)
3518 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3519 d.addCallback(self.failUnlessIsBarJSON)
3520 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt"))
3521 d.addCallback(self.failUnlessIsSubBazDotTxt)
3524 def test_POST_move_file_slash_fail(self):
3525 d = self.shouldFail2(error.Error, "test_POST_rename_file_slash_fail",
3527 "to_name= may not contain a slash",
3528 self.POST, self.public_url + "/foo", t="move",
3529 from_name="bar.txt",
3530 to_name="slash/fail.txt", to_dir="sub")
3531 d.addCallback(lambda res:
3532 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3533 d.addCallback(lambda res:
3534 self.failIfNodeHasChild(self._sub_node, u"slash/fail.txt"))
3535 d.addCallback(lambda ign:
3536 self.shouldFail2(error.Error,
3537 "test_POST_rename_file_slash_fail2",
3539 "from_name= may not contain a slash",
3540 self.POST, self.public_url + "/foo",
3542 from_name="nope/bar.txt",
3543 to_name="fail.txt", to_dir="sub"))
3546 def test_POST_move_file_no_target(self):
3547 d = self.shouldFail2(error.Error, "POST_move_file_no_target",
3549 "move requires from_name and to_dir",
3550 self.POST, self.public_url + "/foo", t="move",
3551 from_name="bar.txt", to_name="baz.txt")
3552 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3553 d.addCallback(self.failUnlessIsBarDotTxt)
3554 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3555 d.addCallback(self.failUnlessIsBarJSON)
3556 d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt"))
3557 d.addCallback(self.failUnlessIsBazDotTxt)
3560 def test_POST_move_file_bad_target_type(self):
3561 d = self.shouldFail2(error.Error, "test_POST_move_file_bad_target_type",
3562 "400 Bad Request", "invalid target_type parameter",
3564 self.public_url + "/foo", t="move",
3565 target_type="*D", from_name="bar.txt",
3569 def test_POST_move_file_multi_level(self):
3570 d = self.POST(self.public_url + "/foo/sub/level2?t=mkdir", "")
3571 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t="move",
3572 from_name="bar.txt", to_dir="sub/level2"))
3573 d.addCallback(lambda res: self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3574 d.addCallback(lambda res: self.failIfNodeHasChild(self._sub_node, u"bar.txt"))
3575 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/level2/bar.txt"))
3576 d.addCallback(self.failUnlessIsBarDotTxt)
3577 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/level2/bar.txt?t=json"))
3578 d.addCallback(self.failUnlessIsBarJSON)
3581 def test_POST_move_file_to_uri(self):
3582 d = self.POST(self.public_url + "/foo", t="move", target_type="uri",
3583 from_name="bar.txt", to_dir=self._sub_uri)
3584 d.addCallback(lambda res:
3585 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3586 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt"))
3587 d.addCallback(self.failUnlessIsBarDotTxt)
3588 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt?t=json"))
3589 d.addCallback(self.failUnlessIsBarJSON)
3592 def test_POST_move_file_to_nonexist_dir(self):
3593 d = self.shouldFail2(error.Error, "POST_move_file_to_nonexist_dir",
3594 "404 Not Found", "No such child: nopechucktesta",
3595 self.POST, self.public_url + "/foo", t="move",
3596 from_name="bar.txt", to_dir="nopechucktesta")
3599 def test_POST_move_file_into_file(self):
3600 d = self.shouldFail2(error.Error, "POST_move_file_into_file",
3601 "400 Bad Request", "to_dir is not a directory",
3602 self.POST, self.public_url + "/foo", t="move",
3603 from_name="bar.txt", to_dir="baz.txt")
3604 d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt"))
3605 d.addCallback(self.failUnlessIsBazDotTxt)
3606 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3607 d.addCallback(self.failUnlessIsBarDotTxt)
3608 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3609 d.addCallback(self.failUnlessIsBarJSON)
3612 def test_POST_move_file_to_bad_uri(self):
3613 d = self.shouldFail2(error.Error, "POST_move_file_to_bad_uri",
3614 "400 Bad Request", "to_dir is not a directory",
3615 self.POST, self.public_url + "/foo", t="move",
3616 from_name="bar.txt", target_type="uri",
3617 to_dir="URI:DIR2:mn5jlyjnrjeuydyswlzyui72i:rmneifcj6k6sycjljjhj3f6majsq2zqffydnnul5hfa4j577arma")
3618 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3619 d.addCallback(self.failUnlessIsBarDotTxt)
3620 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3621 d.addCallback(self.failUnlessIsBarJSON)
3624 def test_POST_move_dir(self):
3625 d = self.POST(self.public_url + "/foo", t="move",
3626 from_name="bar.txt", to_dir="empty")
3627 d.addCallback(lambda res: self.POST(self.public_url + "/foo",
3628 t="move", from_name="empty", to_dir="sub"))
3629 d.addCallback(lambda res:
3630 self.failIfNodeHasChild(self._foo_node, u"empty"))
3631 d.addCallback(lambda res:
3632 self.failUnlessNodeHasChild(self._sub_node, u"empty"))
3633 d.addCallback(lambda res:
3634 self._sub_node.get_child_at_path(u"empty"))
3635 d.addCallback(lambda node:
3636 self.failUnlessNodeHasChild(node, u"bar.txt"))
3637 d.addCallback(lambda res:
3638 self.GET(self.public_url + "/foo/sub/empty/bar.txt"))
3639 d.addCallback(self.failUnlessIsBarDotTxt)
3642 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
3643 """ If target is not None then the redirection has to go to target. If
3644 statuscode is not None then the redirection has to be accomplished with
3645 that HTTP status code."""
3646 if not isinstance(res, failure.Failure):
3647 to_where = (target is None) and "somewhere" or ("to " + target)
3648 self.fail("%s: we were expecting to get redirected %s, not get an"
3649 " actual page: %s" % (which, to_where, res))
3650 res.trap(error.PageRedirect)
3651 if statuscode is not None:
3652 self.failUnlessReallyEqual(res.value.status, statuscode,
3653 "%s: not a redirect" % which)
3654 if target is not None:
3655 # the PageRedirect does not seem to capture the uri= query arg
3656 # properly, so we can't check for it.
3657 realtarget = self.webish_url + target
3658 self.failUnlessReallyEqual(res.value.location, realtarget,
3659 "%s: wrong target" % which)
3660 return res.value.location
3662 def test_GET_URI_form(self):
3663 base = "/uri?uri=%s" % self._bar_txt_uri
3664 # this is supposed to give us a redirect to /uri/$URI, plus arguments
3665 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
3667 d.addBoth(self.shouldRedirect, targetbase)
3668 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
3669 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
3670 d.addCallback(lambda res: self.GET(base+"&t=json"))
3671 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
3672 d.addCallback(self.log, "about to get file by uri")
3673 d.addCallback(lambda res: self.GET(base, followRedirect=True))
3674 d.addCallback(self.failUnlessIsBarDotTxt)
3675 d.addCallback(self.log, "got file by uri, about to get dir by uri")
3676 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
3677 followRedirect=True))
3678 d.addCallback(self.failUnlessIsFooJSON)
3679 d.addCallback(self.log, "got dir by uri")
3683 def test_GET_URI_form_bad(self):
3684 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
3685 "400 Bad Request", "GET /uri requires uri=",
3689 def test_GET_rename_form(self):
3690 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
3691 followRedirect=True)
3693 self.failUnlessIn('name="when_done" value="."', res)
3694 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
3695 self.failUnlessIn(FAVICON_MARKUP, res)
3696 d.addCallback(_check)
3699 def log(self, res, msg):
3700 #print "MSG: %s RES: %s" % (msg, res)
3704 def test_GET_URI_URL(self):
3705 base = "/uri/%s" % self._bar_txt_uri
3707 d.addCallback(self.failUnlessIsBarDotTxt)
3708 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
3709 d.addCallback(self.failUnlessIsBarDotTxt)
3710 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
3711 d.addCallback(self.failUnlessIsBarDotTxt)
3714 def test_GET_URI_URL_dir(self):
3715 base = "/uri/%s?t=json" % self._foo_uri
3717 d.addCallback(self.failUnlessIsFooJSON)
3720 def test_GET_URI_URL_missing(self):
3721 base = "/uri/%s" % self._bad_file_uri
3722 d = self.shouldHTTPError("test_GET_URI_URL_missing",
3723 http.GONE, None, "NotEnoughSharesError",
3725 # TODO: how can we exercise both sides of WebDownloadTarget.fail
3726 # here? we must arrange for a download to fail after target.open()
3727 # has been called, and then inspect the response to see that it is
3728 # shorter than we expected.
3731 def test_PUT_DIRURL_uri(self):
3732 d = self.s.create_dirnode()
3734 new_uri = dn.get_uri()
3735 # replace /foo with a new (empty) directory
3736 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
3737 d.addCallback(lambda res:
3738 self.failUnlessReallyEqual(res.strip(), new_uri))
3739 d.addCallback(lambda res:
3740 self.failUnlessRWChildURIIs(self.public_root,
3744 d.addCallback(_made_dir)
3747 def test_PUT_DIRURL_uri_noreplace(self):
3748 d = self.s.create_dirnode()
3750 new_uri = dn.get_uri()
3751 # replace /foo with a new (empty) directory, but ask that
3752 # replace=false, so it should fail
3753 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
3754 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
3756 self.public_url + "/foo?t=uri&replace=false",
3758 d.addCallback(lambda res:
3759 self.failUnlessRWChildURIIs(self.public_root,
3763 d.addCallback(_made_dir)
3766 def test_PUT_DIRURL_bad_t(self):
3767 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
3768 "400 Bad Request", "PUT to a directory",
3769 self.PUT, self.public_url + "/foo?t=BOGUS", "")
3770 d.addCallback(lambda res:
3771 self.failUnlessRWChildURIIs(self.public_root,
3776 def test_PUT_NEWFILEURL_uri(self):
3777 contents, n, new_uri = self.makefile(8)
3778 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
3779 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3780 d.addCallback(lambda res:
3781 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3785 def test_PUT_NEWFILEURL_mdmf(self):
3786 new_contents = self.NEWFILE_CONTENTS * 300000
3787 d = self.PUT(self.public_url + \
3788 "/foo/mdmf.txt?format=mdmf",
3790 d.addCallback(lambda ignored:
3791 self.GET(self.public_url + "/foo/mdmf.txt?t=json"))
3792 def _got_json(json):
3793 data = simplejson.loads(json)
3795 self.failUnlessIn("format", data)
3796 self.failUnlessEqual(data["format"], "MDMF")
3797 self.failUnless(data['rw_uri'].startswith("URI:MDMF"))
3798 self.failUnless(data['ro_uri'].startswith("URI:MDMF"))
3799 d.addCallback(_got_json)
3802 def test_PUT_NEWFILEURL_sdmf(self):
3803 new_contents = self.NEWFILE_CONTENTS * 300000
3804 d = self.PUT(self.public_url + \
3805 "/foo/sdmf.txt?format=sdmf",
3807 d.addCallback(lambda ignored:
3808 self.GET(self.public_url + "/foo/sdmf.txt?t=json"))
3809 def _got_json(json):
3810 data = simplejson.loads(json)
3812 self.failUnlessIn("format", data)
3813 self.failUnlessEqual(data["format"], "SDMF")
3814 d.addCallback(_got_json)
3817 def test_PUT_NEWFILEURL_bad_format(self):
3818 new_contents = self.NEWFILE_CONTENTS * 300000
3819 return self.shouldHTTPError("PUT_NEWFILEURL_bad_format",
3820 400, "Bad Request", "Unknown format: foo",
3821 self.PUT, self.public_url + \
3822 "/foo/foo.txt?format=foo",
3825 def test_PUT_NEWFILEURL_uri_replace(self):
3826 contents, n, new_uri = self.makefile(8)
3827 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
3828 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3829 d.addCallback(lambda res:
3830 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3834 def test_PUT_NEWFILEURL_uri_no_replace(self):
3835 contents, n, new_uri = self.makefile(8)
3836 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
3837 d.addBoth(self.shouldFail, error.Error,
3838 "PUT_NEWFILEURL_uri_no_replace",
3840 "There was already a child by that name, and you asked me "
3841 "to not replace it")
3844 def test_PUT_NEWFILEURL_uri_unknown_bad(self):
3845 d = self.PUT(self.public_url + "/foo/put-future.txt?t=uri", unknown_rwcap)
3846 d.addBoth(self.shouldFail, error.Error,
3847 "POST_put_uri_unknown_bad",
3849 "unknown cap in a write slot")
3852 def test_PUT_NEWFILEURL_uri_unknown_ro_good(self):
3853 d = self.PUT(self.public_url + "/foo/put-future-ro.txt?t=uri", unknown_rocap)
3854 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3855 u"put-future-ro.txt")
3858 def test_PUT_NEWFILEURL_uri_unknown_imm_good(self):
3859 d = self.PUT(self.public_url + "/foo/put-future-imm.txt?t=uri", unknown_immcap)
3860 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3861 u"put-future-imm.txt")
3864 def test_PUT_NEWFILE_URI(self):
3865 file_contents = "New file contents here\n"
3866 d = self.PUT("/uri", file_contents)
3868 assert isinstance(uri, str), uri
3869 self.failUnlessIn(uri, self.get_all_contents())
3870 self.failUnlessReallyEqual(self.get_all_contents()[uri],
3872 return self.GET("/uri/%s" % uri)
3873 d.addCallback(_check)
3875 self.failUnlessReallyEqual(res, file_contents)
3876 d.addCallback(_check2)
3879 def test_PUT_NEWFILE_URI_not_mutable(self):
3880 file_contents = "New file contents here\n"
3881 d = self.PUT("/uri?mutable=false", file_contents)
3883 assert isinstance(uri, str), uri
3884 self.failUnlessIn(uri, self.get_all_contents())
3885 self.failUnlessReallyEqual(self.get_all_contents()[uri],
3887 return self.GET("/uri/%s" % uri)
3888 d.addCallback(_check)
3890 self.failUnlessReallyEqual(res, file_contents)
3891 d.addCallback(_check2)
3894 def test_PUT_NEWFILE_URI_only_PUT(self):
3895 d = self.PUT("/uri?t=bogus", "")
3896 d.addBoth(self.shouldFail, error.Error,
3897 "PUT_NEWFILE_URI_only_PUT",
3899 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
3902 def test_PUT_NEWFILE_URI_mutable(self):
3903 file_contents = "New file contents here\n"
3904 d = self.PUT("/uri?mutable=true", file_contents)
3905 def _check1(filecap):
3906 filecap = filecap.strip()
3907 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
3908 self.filecap = filecap
3909 u = uri.WriteableSSKFileURI.init_from_string(filecap)
3910 self.failUnlessIn(u.get_storage_index(), self.get_all_contents())
3911 n = self.s.create_node_from_uri(filecap)
3912 return n.download_best_version()
3913 d.addCallback(_check1)
3915 self.failUnlessReallyEqual(data, file_contents)
3916 return self.GET("/uri/%s" % urllib.quote(self.filecap))
3917 d.addCallback(_check2)
3919 self.failUnlessReallyEqual(res, file_contents)
3920 d.addCallback(_check3)
3923 def test_PUT_mkdir(self):
3924 d = self.PUT("/uri?t=mkdir", "")
3926 n = self.s.create_node_from_uri(uri.strip())
3927 d2 = self.failUnlessNodeKeysAre(n, [])
3928 d2.addCallback(lambda res:
3929 self.GET("/uri/%s?t=json" % uri))
3931 d.addCallback(_check)
3932 d.addCallback(self.failUnlessIsEmptyJSON)
3935 def test_PUT_mkdir_mdmf(self):
3936 d = self.PUT("/uri?t=mkdir&format=mdmf", "")
3938 u = uri.from_string(res)
3939 # Check that this is an MDMF writecap
3940 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
3944 def test_PUT_mkdir_sdmf(self):
3945 d = self.PUT("/uri?t=mkdir&format=sdmf", "")
3947 u = uri.from_string(res)
3948 self.failUnlessIsInstance(u, uri.DirectoryURI)
3952 def test_PUT_mkdir_bad_format(self):
3953 return self.shouldHTTPError("PUT_mkdir_bad_format",
3954 400, "Bad Request", "Unknown format: foo",
3955 self.PUT, "/uri?t=mkdir&format=foo",
3958 def test_POST_check(self):
3959 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
3961 # this returns a string form of the results, which are probably
3962 # None since we're using fake filenodes.
3963 # TODO: verify that the check actually happened, by changing
3964 # FakeCHKFileNode to count how many times .check() has been
3967 d.addCallback(_done)
3971 def test_PUT_update_at_offset(self):
3972 file_contents = "test file" * 100000 # about 900 KiB
3973 d = self.PUT("/uri?mutable=true", file_contents)
3975 self.filecap = filecap
3976 new_data = file_contents[:100]
3977 new = "replaced and so on"
3979 new_data += file_contents[len(new_data):]
3980 assert len(new_data) == len(file_contents)
3981 self.new_data = new_data
3982 d.addCallback(_then)
3983 d.addCallback(lambda ignored:
3984 self.PUT("/uri/%s?replace=True&offset=100" % self.filecap,
3985 "replaced and so on"))
3986 def _get_data(filecap):
3987 n = self.s.create_node_from_uri(filecap)
3988 return n.download_best_version()
3989 d.addCallback(_get_data)
3990 d.addCallback(lambda results:
3991 self.failUnlessEqual(results, self.new_data))
3992 # Now try appending things to the file
3993 d.addCallback(lambda ignored:
3994 self.PUT("/uri/%s?offset=%d" % (self.filecap, len(self.new_data)),
3996 d.addCallback(_get_data)
3997 d.addCallback(lambda results:
3998 self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
3999 # and try replacing the beginning of the file
4000 d.addCallback(lambda ignored:
4001 self.PUT("/uri/%s?offset=0" % self.filecap, "begin"))
4002 d.addCallback(_get_data)
4003 d.addCallback(lambda results:
4004 self.failUnlessEqual(results, "begin"+self.new_data[len("begin"):]+("puppies"*100)))
4007 def test_PUT_update_at_invalid_offset(self):
4008 file_contents = "test file" * 100000 # about 900 KiB
4009 d = self.PUT("/uri?mutable=true", file_contents)
4011 self.filecap = filecap
4012 d.addCallback(_then)
4013 # Negative offsets should cause an error.
4014 d.addCallback(lambda ignored:
4015 self.shouldHTTPError("PUT_update_at_invalid_offset",
4019 "/uri/%s?offset=-1" % self.filecap,
4023 def test_PUT_update_at_offset_immutable(self):
4024 file_contents = "Test file" * 100000
4025 d = self.PUT("/uri", file_contents)
4027 self.filecap = filecap
4028 d.addCallback(_then)
4029 d.addCallback(lambda ignored:
4030 self.shouldHTTPError("PUT_update_at_offset_immutable",
4034 "/uri/%s?offset=50" % self.filecap,
4039 def test_bad_method(self):
4040 url = self.webish_url + self.public_url + "/foo/bar.txt"
4041 d = self.shouldHTTPError("bad_method",
4042 501, "Not Implemented",
4043 "I don't know how to treat a BOGUS request.",
4044 client.getPage, url, method="BOGUS")
4047 def test_short_url(self):
4048 url = self.webish_url + "/uri"
4049 d = self.shouldHTTPError("short_url", 501, "Not Implemented",
4050 "I don't know how to treat a DELETE request.",
4051 client.getPage, url, method="DELETE")
4054 def test_ophandle_bad(self):
4055 url = self.webish_url + "/operations/bogus?t=status"
4056 d = self.shouldHTTPError("ophandle_bad", 404, "404 Not Found",
4057 "unknown/expired handle 'bogus'",
4058 client.getPage, url)
4061 def test_ophandle_cancel(self):
4062 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
4063 followRedirect=True)
4064 d.addCallback(lambda ignored:
4065 self.GET("/operations/128?t=status&output=JSON"))
4067 data = simplejson.loads(res)
4068 self.failUnless("finished" in data, res)
4069 monitor = self.ws.root.child_operations.handles["128"][0]
4070 d = self.POST("/operations/128?t=cancel&output=JSON")
4072 data = simplejson.loads(res)
4073 self.failUnless("finished" in data, res)
4074 # t=cancel causes the handle to be forgotten
4075 self.failUnless(monitor.is_cancelled())
4076 d.addCallback(_check2)
4078 d.addCallback(_check1)
4079 d.addCallback(lambda ignored:
4080 self.shouldHTTPError("ophandle_cancel",
4081 404, "404 Not Found",
4082 "unknown/expired handle '128'",
4084 "/operations/128?t=status&output=JSON"))
4087 def test_ophandle_retainfor(self):
4088 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
4089 followRedirect=True)
4090 d.addCallback(lambda ignored:
4091 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
4093 data = simplejson.loads(res)
4094 self.failUnless("finished" in data, res)
4095 d.addCallback(_check1)
4096 # the retain-for=0 will cause the handle to be expired very soon
4097 d.addCallback(lambda ign:
4098 self.clock.advance(2.0))
4099 d.addCallback(lambda ignored:
4100 self.shouldHTTPError("ophandle_retainfor",
4101 404, "404 Not Found",
4102 "unknown/expired handle '129'",
4104 "/operations/129?t=status&output=JSON"))
4107 def test_ophandle_release_after_complete(self):
4108 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
4109 followRedirect=True)
4110 d.addCallback(self.wait_for_operation, "130")
4111 d.addCallback(lambda ignored:
4112 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
4113 # the release-after-complete=true will cause the handle to be expired
4114 d.addCallback(lambda ignored:
4115 self.shouldHTTPError("ophandle_release_after_complete",
4116 404, "404 Not Found",
4117 "unknown/expired handle '130'",
4119 "/operations/130?t=status&output=JSON"))
4122 def test_uncollected_ophandle_expiration(self):
4123 # uncollected ophandles should expire after 4 days
4124 def _make_uncollected_ophandle(ophandle):
4125 d = self.POST(self.public_url +
4126 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
4127 followRedirect=False)
4128 # When we start the operation, the webapi server will want
4129 # to redirect us to the page for the ophandle, so we get
4130 # confirmation that the operation has started. If the
4131 # manifest operation has finished by the time we get there,
4132 # following that redirect (by setting followRedirect=True
4133 # above) has the side effect of collecting the ophandle that
4134 # we've just created, which means that we can't use the
4135 # ophandle to test the uncollected timeout anymore. So,
4136 # instead, catch the 302 here and don't follow it.
4137 d.addBoth(self.should302, "uncollected_ophandle_creation")
4139 # Create an ophandle, don't collect it, then advance the clock by
4140 # 4 days - 1 second and make sure that the ophandle is still there.
4141 d = _make_uncollected_ophandle(131)
4142 d.addCallback(lambda ign:
4143 self.clock.advance((96*60*60) - 1)) # 96 hours = 4 days
4144 d.addCallback(lambda ign:
4145 self.GET("/operations/131?t=status&output=JSON"))
4147 data = simplejson.loads(res)
4148 self.failUnless("finished" in data, res)
4149 d.addCallback(_check1)
4150 # Create an ophandle, don't collect it, then try to collect it
4151 # after 4 days. It should be gone.
4152 d.addCallback(lambda ign:
4153 _make_uncollected_ophandle(132))
4154 d.addCallback(lambda ign:
4155 self.clock.advance(96*60*60))
4156 d.addCallback(lambda ign:
4157 self.shouldHTTPError("uncollected_ophandle_expired_after_100_hours",
4158 404, "404 Not Found",
4159 "unknown/expired handle '132'",
4161 "/operations/132?t=status&output=JSON"))
4164 def test_collected_ophandle_expiration(self):
4165 # collected ophandles should expire after 1 day
4166 def _make_collected_ophandle(ophandle):
4167 d = self.POST(self.public_url +
4168 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
4169 followRedirect=True)
4170 # By following the initial redirect, we collect the ophandle
4171 # we've just created.
4173 # Create a collected ophandle, then collect it after 23 hours
4174 # and 59 seconds to make sure that it is still there.
4175 d = _make_collected_ophandle(133)
4176 d.addCallback(lambda ign:
4177 self.clock.advance((24*60*60) - 1))
4178 d.addCallback(lambda ign:
4179 self.GET("/operations/133?t=status&output=JSON"))
4181 data = simplejson.loads(res)
4182 self.failUnless("finished" in data, res)
4183 d.addCallback(_check1)
4184 # Create another uncollected ophandle, then try to collect it
4185 # after 24 hours to make sure that it is gone.
4186 d.addCallback(lambda ign:
4187 _make_collected_ophandle(134))
4188 d.addCallback(lambda ign:
4189 self.clock.advance(24*60*60))
4190 d.addCallback(lambda ign:
4191 self.shouldHTTPError("collected_ophandle_expired_after_1_day",
4192 404, "404 Not Found",
4193 "unknown/expired handle '134'",
4195 "/operations/134?t=status&output=JSON"))
4198 def test_incident(self):
4199 d = self.POST("/report_incident", details="eek")
4201 self.failIfIn("<html>", res)
4202 self.failUnlessIn("Thank you for your report!", res)
4203 d.addCallback(_done)
4206 def test_static(self):
4207 webdir = os.path.join(self.staticdir, "subdir")
4208 fileutil.make_dirs(webdir)
4209 f = open(os.path.join(webdir, "hello.txt"), "wb")
4213 d = self.GET("/static/subdir/hello.txt")
4215 self.failUnlessReallyEqual(res, "hello")
4216 d.addCallback(_check)
4220 class IntroducerWeb(unittest.TestCase):
4225 d = defer.succeed(None)
4227 d.addCallback(lambda ign: self.node.stopService())
4228 d.addCallback(flushEventualQueue)
4231 def test_welcome(self):
4232 basedir = "web.IntroducerWeb.test_welcome"
4234 fileutil.write(os.path.join(basedir, "tahoe.cfg"), "[node]\nweb.port = tcp:0\n")
4235 self.node = IntroducerNode(basedir)
4236 self.ws = self.node.getServiceNamed("webish")
4238 d = fireEventually(None)
4239 d.addCallback(lambda ign: self.node.startService())
4240 d.addCallback(lambda ign: self.node.when_tub_ready())
4242 d.addCallback(lambda ign: self.GET("/"))
4244 self.failUnlessIn('Welcome to the Tahoe-LAFS Introducer', res)
4245 self.failUnlessIn(FAVICON_MARKUP, res)
4246 d.addCallback(_check)
4249 def GET(self, urlpath, followRedirect=False, return_response=False,
4251 # if return_response=True, this fires with (data, statuscode,
4252 # respheaders) instead of just data.
4253 assert not isinstance(urlpath, unicode)
4254 url = self.ws.getURL().rstrip('/') + urlpath
4255 factory = HTTPClientGETFactory(url, method="GET",
4256 followRedirect=followRedirect, **kwargs)
4257 reactor.connectTCP("localhost", self.ws.getPortnum(), factory)
4258 d = factory.deferred
4259 def _got_data(data):
4260 return (data, factory.status, factory.response_headers)
4262 d.addCallback(_got_data)
4263 return factory.deferred
4266 class Util(ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
4267 def test_load_file(self):
4268 # This will raise an exception unless a well-formed XML file is found under that name.
4269 common.getxmlfile('directory.xhtml').load()
4271 def test_parse_replace_arg(self):
4272 self.failUnlessReallyEqual(common.parse_replace_arg("true"), True)
4273 self.failUnlessReallyEqual(common.parse_replace_arg("false"), False)
4274 self.failUnlessReallyEqual(common.parse_replace_arg("only-files"),
4276 self.shouldFail(AssertionError, "test_parse_replace_arg", "",
4277 common.parse_replace_arg, "only_fles")
4279 def test_abbreviate_time(self):
4280 self.failUnlessReallyEqual(common.abbreviate_time(None), "")
4281 self.failUnlessReallyEqual(common.abbreviate_time(1.234), "1.23s")
4282 self.failUnlessReallyEqual(common.abbreviate_time(0.123), "123ms")
4283 self.failUnlessReallyEqual(common.abbreviate_time(0.00123), "1.2ms")
4284 self.failUnlessReallyEqual(common.abbreviate_time(0.000123), "123us")
4285 self.failUnlessReallyEqual(common.abbreviate_time(-123000), "-123000000000us")
4287 def test_compute_rate(self):
4288 self.failUnlessReallyEqual(common.compute_rate(None, None), None)
4289 self.failUnlessReallyEqual(common.compute_rate(None, 1), None)
4290 self.failUnlessReallyEqual(common.compute_rate(250000, None), None)
4291 self.failUnlessReallyEqual(common.compute_rate(250000, 0), None)
4292 self.failUnlessReallyEqual(common.compute_rate(250000, 10), 25000.0)
4293 self.failUnlessReallyEqual(common.compute_rate(0, 10), 0.0)
4294 self.shouldFail(AssertionError, "test_compute_rate", "",
4295 common.compute_rate, -100, 10)
4296 self.shouldFail(AssertionError, "test_compute_rate", "",
4297 common.compute_rate, 100, -10)
4300 rate = common.compute_rate(10*1000*1000, 1)
4301 self.failUnlessReallyEqual(common.abbreviate_rate(rate), "10.00MBps")
4303 def test_abbreviate_rate(self):
4304 self.failUnlessReallyEqual(common.abbreviate_rate(None), "")
4305 self.failUnlessReallyEqual(common.abbreviate_rate(1234000), "1.23MBps")
4306 self.failUnlessReallyEqual(common.abbreviate_rate(12340), "12.3kBps")
4307 self.failUnlessReallyEqual(common.abbreviate_rate(123), "123Bps")
4309 def test_abbreviate_size(self):
4310 self.failUnlessReallyEqual(common.abbreviate_size(None), "")
4311 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
4312 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
4313 self.failUnlessReallyEqual(common.abbreviate_size(1230), "1.2kB")
4314 self.failUnlessReallyEqual(common.abbreviate_size(123), "123B")
4316 def test_plural(self):
4318 return "%d second%s" % (s, status.plural(s))
4319 self.failUnlessReallyEqual(convert(0), "0 seconds")
4320 self.failUnlessReallyEqual(convert(1), "1 second")
4321 self.failUnlessReallyEqual(convert(2), "2 seconds")
4323 return "has share%s: %s" % (status.plural(s), ",".join(s))
4324 self.failUnlessReallyEqual(convert2([]), "has shares: ")
4325 self.failUnlessReallyEqual(convert2(["1"]), "has share: 1")
4326 self.failUnlessReallyEqual(convert2(["1","2"]), "has shares: 1,2")
4329 class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
4331 def CHECK(self, ign, which, args, clientnum=0):
4332 fileurl = self.fileurls[which]
4333 url = fileurl + "?" + args
4334 return self.GET(url, method="POST", clientnum=clientnum)
4336 def test_filecheck(self):
4337 self.basedir = "web/Grid/filecheck"
4339 c0 = self.g.clients[0]
4342 d = c0.upload(upload.Data(DATA, convergence=""))
4343 def _stash_uri(ur, which):
4344 self.uris[which] = ur.get_uri()
4345 d.addCallback(_stash_uri, "good")
4346 d.addCallback(lambda ign:
4347 c0.upload(upload.Data(DATA+"1", convergence="")))
4348 d.addCallback(_stash_uri, "sick")
4349 d.addCallback(lambda ign:
4350 c0.upload(upload.Data(DATA+"2", convergence="")))
4351 d.addCallback(_stash_uri, "dead")
4352 def _stash_mutable_uri(n, which):
4353 self.uris[which] = n.get_uri()
4354 assert isinstance(self.uris[which], str)
4355 d.addCallback(lambda ign:
4356 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4357 d.addCallback(_stash_mutable_uri, "corrupt")
4358 d.addCallback(lambda ign:
4359 c0.upload(upload.Data("literal", convergence="")))
4360 d.addCallback(_stash_uri, "small")
4361 d.addCallback(lambda ign: c0.create_immutable_dirnode({}))
4362 d.addCallback(_stash_mutable_uri, "smalldir")
4364 def _compute_fileurls(ignored):
4366 for which in self.uris:
4367 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4368 d.addCallback(_compute_fileurls)
4370 def _clobber_shares(ignored):
4371 good_shares = self.find_uri_shares(self.uris["good"])
4372 self.failUnlessReallyEqual(len(good_shares), 10)
4373 sick_shares = self.find_uri_shares(self.uris["sick"])
4374 os.unlink(sick_shares[0][2])
4375 dead_shares = self.find_uri_shares(self.uris["dead"])
4376 for i in range(1, 10):
4377 os.unlink(dead_shares[i][2])
4378 c_shares = self.find_uri_shares(self.uris["corrupt"])
4379 cso = CorruptShareOptions()
4380 cso.stdout = StringIO()
4381 cso.parseOptions([c_shares[0][2]])
4383 d.addCallback(_clobber_shares)
4385 d.addCallback(self.CHECK, "good", "t=check")
4386 def _got_html_good(res):
4387 self.failUnlessIn("Healthy", res)
4388 self.failIfIn("Not Healthy", res)
4389 self.failUnlessIn(FAVICON_MARKUP, res)
4390 d.addCallback(_got_html_good)
4391 d.addCallback(self.CHECK, "good", "t=check&return_to=somewhere")
4392 def _got_html_good_return_to(res):
4393 self.failUnlessIn("Healthy", res)
4394 self.failIfIn("Not Healthy", res)
4395 self.failUnlessIn('<a href="somewhere">Return to file', res)
4396 d.addCallback(_got_html_good_return_to)
4397 d.addCallback(self.CHECK, "good", "t=check&output=json")
4398 def _got_json_good(res):
4399 r = simplejson.loads(res)
4400 self.failUnlessEqual(r["summary"], "Healthy")
4401 self.failUnless(r["results"]["healthy"])
4402 self.failIf(r["results"]["needs-rebalancing"])
4403 self.failUnless(r["results"]["recoverable"])
4404 d.addCallback(_got_json_good)
4406 d.addCallback(self.CHECK, "small", "t=check")
4407 def _got_html_small(res):
4408 self.failUnlessIn("Literal files are always healthy", res)
4409 self.failIfIn("Not Healthy", res)
4410 d.addCallback(_got_html_small)
4411 d.addCallback(self.CHECK, "small", "t=check&return_to=somewhere")
4412 def _got_html_small_return_to(res):
4413 self.failUnlessIn("Literal files are always healthy", res)
4414 self.failIfIn("Not Healthy", res)
4415 self.failUnlessIn('<a href="somewhere">Return to file', res)
4416 d.addCallback(_got_html_small_return_to)
4417 d.addCallback(self.CHECK, "small", "t=check&output=json")
4418 def _got_json_small(res):
4419 r = simplejson.loads(res)
4420 self.failUnlessEqual(r["storage-index"], "")
4421 self.failUnless(r["results"]["healthy"])
4422 d.addCallback(_got_json_small)
4424 d.addCallback(self.CHECK, "smalldir", "t=check")
4425 def _got_html_smalldir(res):
4426 self.failUnlessIn("Literal files are always healthy", res)
4427 self.failIfIn("Not Healthy", res)
4428 d.addCallback(_got_html_smalldir)
4429 d.addCallback(self.CHECK, "smalldir", "t=check&output=json")
4430 def _got_json_smalldir(res):
4431 r = simplejson.loads(res)
4432 self.failUnlessEqual(r["storage-index"], "")
4433 self.failUnless(r["results"]["healthy"])
4434 d.addCallback(_got_json_smalldir)
4436 d.addCallback(self.CHECK, "sick", "t=check")
4437 def _got_html_sick(res):
4438 self.failUnlessIn("Not Healthy", res)
4439 d.addCallback(_got_html_sick)
4440 d.addCallback(self.CHECK, "sick", "t=check&output=json")
4441 def _got_json_sick(res):
4442 r = simplejson.loads(res)
4443 self.failUnlessEqual(r["summary"],
4444 "Not Healthy: 9 shares (enc 3-of-10)")
4445 self.failIf(r["results"]["healthy"])
4446 self.failIf(r["results"]["needs-rebalancing"])
4447 self.failUnless(r["results"]["recoverable"])
4448 d.addCallback(_got_json_sick)
4450 d.addCallback(self.CHECK, "dead", "t=check")
4451 def _got_html_dead(res):
4452 self.failUnlessIn("Not Healthy", res)
4453 d.addCallback(_got_html_dead)
4454 d.addCallback(self.CHECK, "dead", "t=check&output=json")
4455 def _got_json_dead(res):
4456 r = simplejson.loads(res)
4457 self.failUnlessEqual(r["summary"],
4458 "Not Healthy: 1 shares (enc 3-of-10)")
4459 self.failIf(r["results"]["healthy"])
4460 self.failIf(r["results"]["needs-rebalancing"])
4461 self.failIf(r["results"]["recoverable"])
4462 d.addCallback(_got_json_dead)
4464 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true")
4465 def _got_html_corrupt(res):
4466 self.failUnlessIn("Not Healthy! : Unhealthy", res)
4467 d.addCallback(_got_html_corrupt)
4468 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&output=json")
4469 def _got_json_corrupt(res):
4470 r = simplejson.loads(res)
4471 self.failUnlessIn("Unhealthy: 9 shares (enc 3-of-10)", r["summary"])
4472 self.failIf(r["results"]["healthy"])
4473 self.failUnless(r["results"]["recoverable"])
4474 self.failUnlessReallyEqual(r["results"]["count-shares-good"], 9)
4475 self.failUnlessReallyEqual(r["results"]["count-corrupt-shares"], 1)
4476 d.addCallback(_got_json_corrupt)
4478 d.addErrback(self.explain_web_error)
4481 def test_repair_html(self):
4482 self.basedir = "web/Grid/repair_html"
4484 c0 = self.g.clients[0]
4487 d = c0.upload(upload.Data(DATA, convergence=""))
4488 def _stash_uri(ur, which):
4489 self.uris[which] = ur.get_uri()
4490 d.addCallback(_stash_uri, "good")
4491 d.addCallback(lambda ign:
4492 c0.upload(upload.Data(DATA+"1", convergence="")))
4493 d.addCallback(_stash_uri, "sick")
4494 d.addCallback(lambda ign:
4495 c0.upload(upload.Data(DATA+"2", convergence="")))
4496 d.addCallback(_stash_uri, "dead")
4497 def _stash_mutable_uri(n, which):
4498 self.uris[which] = n.get_uri()
4499 assert isinstance(self.uris[which], str)
4500 d.addCallback(lambda ign:
4501 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4502 d.addCallback(_stash_mutable_uri, "corrupt")
4504 def _compute_fileurls(ignored):
4506 for which in self.uris:
4507 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4508 d.addCallback(_compute_fileurls)
4510 def _clobber_shares(ignored):
4511 good_shares = self.find_uri_shares(self.uris["good"])
4512 self.failUnlessReallyEqual(len(good_shares), 10)
4513 sick_shares = self.find_uri_shares(self.uris["sick"])
4514 os.unlink(sick_shares[0][2])
4515 dead_shares = self.find_uri_shares(self.uris["dead"])
4516 for i in range(1, 10):
4517 os.unlink(dead_shares[i][2])
4518 c_shares = self.find_uri_shares(self.uris["corrupt"])
4519 cso = CorruptShareOptions()
4520 cso.stdout = StringIO()
4521 cso.parseOptions([c_shares[0][2]])
4523 d.addCallback(_clobber_shares)
4525 d.addCallback(self.CHECK, "good", "t=check&repair=true")
4526 def _got_html_good(res):
4527 self.failUnlessIn("Healthy", res)
4528 self.failIfIn("Not Healthy", res)
4529 self.failUnlessIn("No repair necessary", res)
4530 self.failUnlessIn(FAVICON_MARKUP, res)
4531 d.addCallback(_got_html_good)
4533 d.addCallback(self.CHECK, "sick", "t=check&repair=true")
4534 def _got_html_sick(res):
4535 self.failUnlessIn("Healthy : healthy", res)
4536 self.failIfIn("Not Healthy", res)
4537 self.failUnlessIn("Repair successful", res)
4538 d.addCallback(_got_html_sick)
4540 # repair of a dead file will fail, of course, but it isn't yet
4541 # clear how this should be reported. Right now it shows up as
4544 #d.addCallback(self.CHECK, "dead", "t=check&repair=true")
4545 #def _got_html_dead(res):
4547 # self.failUnlessIn("Healthy : healthy", res)
4548 # self.failIfIn("Not Healthy", res)
4549 # self.failUnlessIn("No repair necessary", res)
4550 #d.addCallback(_got_html_dead)
4552 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&repair=true")
4553 def _got_html_corrupt(res):
4554 self.failUnlessIn("Healthy : Healthy", res)
4555 self.failIfIn("Not Healthy", res)
4556 self.failUnlessIn("Repair successful", res)
4557 d.addCallback(_got_html_corrupt)
4559 d.addErrback(self.explain_web_error)
4562 def test_repair_json(self):
4563 self.basedir = "web/Grid/repair_json"
4565 c0 = self.g.clients[0]
4568 d = c0.upload(upload.Data(DATA+"1", convergence=""))
4569 def _stash_uri(ur, which):
4570 self.uris[which] = ur.get_uri()
4571 d.addCallback(_stash_uri, "sick")
4573 def _compute_fileurls(ignored):
4575 for which in self.uris:
4576 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4577 d.addCallback(_compute_fileurls)
4579 def _clobber_shares(ignored):
4580 sick_shares = self.find_uri_shares(self.uris["sick"])
4581 os.unlink(sick_shares[0][2])
4582 d.addCallback(_clobber_shares)
4584 d.addCallback(self.CHECK, "sick", "t=check&repair=true&output=json")
4585 def _got_json_sick(res):
4586 r = simplejson.loads(res)
4587 self.failUnlessReallyEqual(r["repair-attempted"], True)
4588 self.failUnlessReallyEqual(r["repair-successful"], True)
4589 self.failUnlessEqual(r["pre-repair-results"]["summary"],
4590 "Not Healthy: 9 shares (enc 3-of-10)")
4591 self.failIf(r["pre-repair-results"]["results"]["healthy"])
4592 self.failUnlessEqual(r["post-repair-results"]["summary"], "healthy")
4593 self.failUnless(r["post-repair-results"]["results"]["healthy"])
4594 d.addCallback(_got_json_sick)
4596 d.addErrback(self.explain_web_error)
4599 def test_unknown(self, immutable=False):
4600 self.basedir = "web/Grid/unknown"
4602 self.basedir = "web/Grid/unknown-immutable"
4605 c0 = self.g.clients[0]
4609 # the future cap format may contain slashes, which must be tolerated
4610 expected_info_url = "uri/%s?t=info" % urllib.quote(unknown_rwcap,
4614 name = u"future-imm"
4615 future_node = UnknownNode(None, unknown_immcap, deep_immutable=True)
4616 d = c0.create_immutable_dirnode({name: (future_node, {})})
4619 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4620 d = c0.create_dirnode()
4622 def _stash_root_and_create_file(n):
4624 self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
4625 self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
4627 return self.rootnode.set_node(name, future_node)
4628 d.addCallback(_stash_root_and_create_file)
4630 # make sure directory listing tolerates unknown nodes
4631 d.addCallback(lambda ign: self.GET(self.rooturl))
4632 def _check_directory_html(res, expected_type_suffix):
4633 pattern = re.compile(r'<td>\?%s</td>[ \t\n\r]*'
4634 '<td>%s</td>' % (expected_type_suffix, str(name)),
4636 self.failUnless(re.search(pattern, res), res)
4637 # find the More Info link for name, should be relative
4638 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4639 info_url = mo.group(1)
4640 self.failUnlessReallyEqual(info_url, "%s?t=info" % (str(name),))
4642 d.addCallback(_check_directory_html, "-IMM")
4644 d.addCallback(_check_directory_html, "")
4646 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4647 def _check_directory_json(res, expect_rw_uri):
4648 data = simplejson.loads(res)
4649 self.failUnlessEqual(data[0], "dirnode")
4650 f = data[1]["children"][name]
4651 self.failUnlessEqual(f[0], "unknown")
4653 self.failUnlessReallyEqual(to_str(f[1]["rw_uri"]), unknown_rwcap, data)
4655 self.failIfIn("rw_uri", f[1])
4657 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_immcap, data)
4659 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_rocap, data)
4660 self.failUnlessIn("metadata", f[1])
4661 d.addCallback(_check_directory_json, expect_rw_uri=not immutable)
4663 def _check_info(res, expect_rw_uri, expect_ro_uri):
4664 self.failUnlessIn("Object Type: <span>unknown</span>", res)
4666 self.failUnlessIn(unknown_rwcap, res)
4669 self.failUnlessIn(unknown_immcap, res)
4671 self.failUnlessIn(unknown_rocap, res)
4673 self.failIfIn(unknown_rocap, res)
4674 self.failIfIn("Raw data as", res)
4675 self.failIfIn("Directory writecap", res)
4676 self.failIfIn("Checker Operations", res)
4677 self.failIfIn("Mutable File Operations", res)
4678 self.failIfIn("Directory Operations", res)
4680 # FIXME: these should have expect_rw_uri=not immutable; I don't know
4681 # why they fail. Possibly related to ticket #922.
4683 d.addCallback(lambda ign: self.GET(expected_info_url))
4684 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False)
4685 d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name))))
4686 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True)
4688 def _check_json(res, expect_rw_uri):
4689 data = simplejson.loads(res)
4690 self.failUnlessEqual(data[0], "unknown")
4692 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), unknown_rwcap, data)
4694 self.failIfIn("rw_uri", data[1])
4697 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_immcap, data)
4698 self.failUnlessReallyEqual(data[1]["mutable"], False)
4700 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4701 self.failUnlessReallyEqual(data[1]["mutable"], True)
4703 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4704 self.failIfIn("mutable", data[1])
4706 # TODO: check metadata contents
4707 self.failUnlessIn("metadata", data[1])
4709 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name))))
4710 d.addCallback(_check_json, expect_rw_uri=not immutable)
4712 # and make sure that a read-only version of the directory can be
4713 # rendered too. This version will not have unknown_rwcap, whether
4714 # or not future_node was immutable.
4715 d.addCallback(lambda ign: self.GET(self.rourl))
4717 d.addCallback(_check_directory_html, "-IMM")
4719 d.addCallback(_check_directory_html, "-RO")
4721 d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
4722 d.addCallback(_check_directory_json, expect_rw_uri=False)
4724 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name))))
4725 d.addCallback(_check_json, expect_rw_uri=False)
4727 # TODO: check that getting t=info from the Info link in the ro directory
4728 # works, and does not include the writecap URI.
4731 def test_immutable_unknown(self):
4732 return self.test_unknown(immutable=True)
4734 def test_mutant_dirnodes_are_omitted(self):
4735 self.basedir = "web/Grid/mutant_dirnodes_are_omitted"
4738 c = self.g.clients[0]
4743 lonely_uri = "URI:LIT:n5xgk" # LIT for "one"
4744 mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
4745 mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
4747 # This method tests mainly dirnode, but we'd have to duplicate code in order to
4748 # test the dirnode and web layers separately.
4750 # 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap,
4751 # and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field.
4752 # When the directory is read, the mutants should be silently disposed of, leaving
4753 # their lonely sibling.
4754 # We don't test the case of a retrieving a cap from the encrypted rw_uri field,
4755 # because immutable directories don't have a writecap and therefore that field
4756 # isn't (and can't be) decrypted.
4757 # TODO: The field still exists in the netstring. Technically we should check what
4758 # happens if something is put there (_unpack_contents should raise ValueError),
4759 # but that can wait.
4761 lonely_child = nm.create_from_cap(lonely_uri)
4762 mutant_ro_child = nm.create_from_cap(mut_read_uri)
4763 mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri)
4765 def _by_hook_or_by_crook():
4767 for n in [mutant_ro_child, mutant_write_in_ro_child]:
4768 n.is_allowed_in_immutable_directory = _by_hook_or_by_crook
4770 mutant_write_in_ro_child.get_write_uri = lambda: None
4771 mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri
4773 kids = {u"lonely": (lonely_child, {}),
4774 u"ro": (mutant_ro_child, {}),
4775 u"write-in-ro": (mutant_write_in_ro_child, {}),
4777 d = c.create_immutable_dirnode(kids)
4780 self.failUnless(isinstance(dn, dirnode.DirectoryNode))
4781 self.failIf(dn.is_mutable())
4782 self.failUnless(dn.is_readonly())
4783 # This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail.
4784 self.failIf(hasattr(dn._node, 'get_writekey'))
4786 self.failUnlessIn("RO-IMM", rep)
4788 self.failUnlessIn("CHK", cap.to_string())
4791 self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/"
4792 return download_to_data(dn._node)
4793 d.addCallback(_created)
4795 def _check_data(data):
4796 # Decode the netstring representation of the directory to check that all children
4797 # are present. This is a bit of an abstraction violation, but there's not really
4798 # any other way to do it given that the real DirectoryNode._unpack_contents would
4799 # strip the mutant children out (which is what we're trying to test, later).
4802 while position < len(data):
4803 entries, position = split_netstring(data, 1, position)
4805 (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
4806 name = name_utf8.decode("utf-8")
4807 self.failUnlessEqual(rwcapdata, "")
4808 self.failUnlessIn(name, kids)
4809 (expected_child, ign) = kids[name]
4810 self.failUnlessReallyEqual(ro_uri, expected_child.get_readonly_uri())
4813 self.failUnlessReallyEqual(numkids, 3)
4814 return self.rootnode.list()
4815 d.addCallback(_check_data)
4817 # Now when we use the real directory listing code, the mutants should be absent.
4818 def _check_kids(children):
4819 self.failUnlessReallyEqual(sorted(children.keys()), [u"lonely"])
4820 lonely_node, lonely_metadata = children[u"lonely"]
4822 self.failUnlessReallyEqual(lonely_node.get_write_uri(), None)
4823 self.failUnlessReallyEqual(lonely_node.get_readonly_uri(), lonely_uri)
4824 d.addCallback(_check_kids)
4826 d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
4827 d.addCallback(lambda n: n.list())
4828 d.addCallback(_check_kids) # again with dirnode recreated from cap
4830 # Make sure the lonely child can be listed in HTML...
4831 d.addCallback(lambda ign: self.GET(self.rooturl))
4832 def _check_html(res):
4833 self.failIfIn("URI:SSK", res)
4834 get_lonely = "".join([r'<td>FILE</td>',
4836 r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),),
4838 r'\s+<td align="right">%d</td>' % len("one"),
4840 self.failUnless(re.search(get_lonely, res), res)
4842 # find the More Info link for name, should be relative
4843 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4844 info_url = mo.group(1)
4845 self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url)
4846 d.addCallback(_check_html)
4849 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4850 def _check_json(res):
4851 data = simplejson.loads(res)
4852 self.failUnlessEqual(data[0], "dirnode")
4853 listed_children = data[1]["children"]
4854 self.failUnlessReallyEqual(sorted(listed_children.keys()), [u"lonely"])
4855 ll_type, ll_data = listed_children[u"lonely"]
4856 self.failUnlessEqual(ll_type, "filenode")
4857 self.failIfIn("rw_uri", ll_data)
4858 self.failUnlessReallyEqual(to_str(ll_data["ro_uri"]), lonely_uri)
4859 d.addCallback(_check_json)
4862 def test_deep_check(self):
4863 self.basedir = "web/Grid/deep_check"
4865 c0 = self.g.clients[0]
4869 d = c0.create_dirnode()
4870 def _stash_root_and_create_file(n):
4872 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4873 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4874 d.addCallback(_stash_root_and_create_file)
4875 def _stash_uri(fn, which):
4876 self.uris[which] = fn.get_uri()
4878 d.addCallback(_stash_uri, "good")
4879 d.addCallback(lambda ign:
4880 self.rootnode.add_file(u"small",
4881 upload.Data("literal",
4883 d.addCallback(_stash_uri, "small")
4884 d.addCallback(lambda ign:
4885 self.rootnode.add_file(u"sick",
4886 upload.Data(DATA+"1",
4888 d.addCallback(_stash_uri, "sick")
4890 # this tests that deep-check and stream-manifest will ignore
4891 # UnknownNode instances. Hopefully this will also cover deep-stats.
4892 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4893 d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node))
4895 def _clobber_shares(ignored):
4896 self.delete_shares_numbered(self.uris["sick"], [0,1])
4897 d.addCallback(_clobber_shares)
4905 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4908 units = [simplejson.loads(line)
4909 for line in res.splitlines()
4912 print "response is:", res
4913 print "undecodeable line was '%s'" % line
4915 self.failUnlessReallyEqual(len(units), 5+1)
4916 # should be parent-first
4918 self.failUnlessEqual(u0["path"], [])
4919 self.failUnlessEqual(u0["type"], "directory")
4920 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4921 u0cr = u0["check-results"]
4922 self.failUnlessReallyEqual(u0cr["results"]["count-shares-good"], 10)
4924 ugood = [u for u in units
4925 if u["type"] == "file" and u["path"] == [u"good"]][0]
4926 self.failUnlessReallyEqual(to_str(ugood["cap"]), self.uris["good"])
4927 ugoodcr = ugood["check-results"]
4928 self.failUnlessReallyEqual(ugoodcr["results"]["count-shares-good"], 10)
4931 self.failUnlessEqual(stats["type"], "stats")
4933 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4934 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4935 self.failUnlessReallyEqual(s["count-directories"], 1)
4936 self.failUnlessReallyEqual(s["count-unknown"], 1)
4937 d.addCallback(_done)
4939 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4940 def _check_manifest(res):
4941 self.failUnless(res.endswith("\n"))
4942 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
4943 self.failUnlessReallyEqual(len(units), 5+1)
4944 self.failUnlessEqual(units[-1]["type"], "stats")
4946 self.failUnlessEqual(first["path"], [])
4947 self.failUnlessEqual(to_str(first["cap"]), self.rootnode.get_uri())
4948 self.failUnlessEqual(first["type"], "directory")
4949 stats = units[-1]["stats"]
4950 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4951 self.failUnlessReallyEqual(stats["count-literal-files"], 1)
4952 self.failUnlessReallyEqual(stats["count-mutable-files"], 0)
4953 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4954 self.failUnlessReallyEqual(stats["count-unknown"], 1)
4955 d.addCallback(_check_manifest)
4957 # now add root/subdir and root/subdir/grandchild, then make subdir
4958 # unrecoverable, then see what happens
4960 d.addCallback(lambda ign:
4961 self.rootnode.create_subdirectory(u"subdir"))
4962 d.addCallback(_stash_uri, "subdir")
4963 d.addCallback(lambda subdir_node:
4964 subdir_node.add_file(u"grandchild",
4965 upload.Data(DATA+"2",
4967 d.addCallback(_stash_uri, "grandchild")
4969 d.addCallback(lambda ign:
4970 self.delete_shares_numbered(self.uris["subdir"],
4978 # root/subdir [unrecoverable]
4979 # root/subdir/grandchild
4981 # how should a streaming-JSON API indicate fatal error?
4982 # answer: emit ERROR: instead of a JSON string
4984 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4985 def _check_broken_manifest(res):
4986 lines = res.splitlines()
4988 for (i,line) in enumerate(lines)
4989 if line.startswith("ERROR:")]
4991 self.fail("no ERROR: in output: %s" % (res,))
4992 first_error = error_lines[0]
4993 error_line = lines[first_error]
4994 error_msg = lines[first_error+1:]
4995 error_msg_s = "\n".join(error_msg) + "\n"
4996 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4998 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4999 units = [simplejson.loads(line) for line in lines[:first_error]]
5000 self.failUnlessReallyEqual(len(units), 6) # includes subdir
5001 last_unit = units[-1]
5002 self.failUnlessEqual(last_unit["path"], ["subdir"])
5003 d.addCallback(_check_broken_manifest)
5005 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
5006 def _check_broken_deepcheck(res):
5007 lines = res.splitlines()
5009 for (i,line) in enumerate(lines)
5010 if line.startswith("ERROR:")]
5012 self.fail("no ERROR: in output: %s" % (res,))
5013 first_error = error_lines[0]
5014 error_line = lines[first_error]
5015 error_msg = lines[first_error+1:]
5016 error_msg_s = "\n".join(error_msg) + "\n"
5017 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
5019 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
5020 units = [simplejson.loads(line) for line in lines[:first_error]]
5021 self.failUnlessReallyEqual(len(units), 6) # includes subdir
5022 last_unit = units[-1]
5023 self.failUnlessEqual(last_unit["path"], ["subdir"])
5024 r = last_unit["check-results"]["results"]
5025 self.failUnlessReallyEqual(r["count-recoverable-versions"], 0)
5026 self.failUnlessReallyEqual(r["count-shares-good"], 1)
5027 self.failUnlessReallyEqual(r["recoverable"], False)
5028 d.addCallback(_check_broken_deepcheck)
5030 d.addErrback(self.explain_web_error)
5033 def test_deep_check_and_repair(self):
5034 self.basedir = "web/Grid/deep_check_and_repair"
5036 c0 = self.g.clients[0]
5040 d = c0.create_dirnode()
5041 def _stash_root_and_create_file(n):
5043 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
5044 return n.add_file(u"good", upload.Data(DATA, convergence=""))
5045 d.addCallback(_stash_root_and_create_file)
5046 def _stash_uri(fn, which):
5047 self.uris[which] = fn.get_uri()
5048 d.addCallback(_stash_uri, "good")
5049 d.addCallback(lambda ign:
5050 self.rootnode.add_file(u"small",
5051 upload.Data("literal",
5053 d.addCallback(_stash_uri, "small")
5054 d.addCallback(lambda ign:
5055 self.rootnode.add_file(u"sick",
5056 upload.Data(DATA+"1",
5058 d.addCallback(_stash_uri, "sick")
5059 #d.addCallback(lambda ign:
5060 # self.rootnode.add_file(u"dead",
5061 # upload.Data(DATA+"2",
5063 #d.addCallback(_stash_uri, "dead")
5065 #d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
5066 #d.addCallback(lambda fn: self.rootnode.set_node(u"corrupt", fn))
5067 #d.addCallback(_stash_uri, "corrupt")
5069 def _clobber_shares(ignored):
5070 good_shares = self.find_uri_shares(self.uris["good"])
5071 self.failUnlessReallyEqual(len(good_shares), 10)
5072 sick_shares = self.find_uri_shares(self.uris["sick"])
5073 os.unlink(sick_shares[0][2])
5074 #dead_shares = self.find_uri_shares(self.uris["dead"])
5075 #for i in range(1, 10):
5076 # os.unlink(dead_shares[i][2])
5078 #c_shares = self.find_uri_shares(self.uris["corrupt"])
5079 #cso = CorruptShareOptions()
5080 #cso.stdout = StringIO()
5081 #cso.parseOptions([c_shares[0][2]])
5083 d.addCallback(_clobber_shares)
5086 # root/good CHK, 10 shares
5088 # root/sick CHK, 9 shares
5090 d.addCallback(self.CHECK, "root", "t=stream-deep-check&repair=true")
5092 units = [simplejson.loads(line)
5093 for line in res.splitlines()
5095 self.failUnlessReallyEqual(len(units), 4+1)
5096 # should be parent-first
5098 self.failUnlessEqual(u0["path"], [])
5099 self.failUnlessEqual(u0["type"], "directory")
5100 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
5101 u0crr = u0["check-and-repair-results"]
5102 self.failUnlessReallyEqual(u0crr["repair-attempted"], False)
5103 self.failUnlessReallyEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
5105 ugood = [u for u in units
5106 if u["type"] == "file" and u["path"] == [u"good"]][0]
5107 self.failUnlessEqual(to_str(ugood["cap"]), self.uris["good"])
5108 ugoodcrr = ugood["check-and-repair-results"]
5109 self.failUnlessReallyEqual(ugoodcrr["repair-attempted"], False)
5110 self.failUnlessReallyEqual(ugoodcrr["pre-repair-results"]["results"]["count-shares-good"], 10)
5112 usick = [u for u in units
5113 if u["type"] == "file" and u["path"] == [u"sick"]][0]
5114 self.failUnlessReallyEqual(to_str(usick["cap"]), self.uris["sick"])
5115 usickcrr = usick["check-and-repair-results"]
5116 self.failUnlessReallyEqual(usickcrr["repair-attempted"], True)
5117 self.failUnlessReallyEqual(usickcrr["repair-successful"], True)
5118 self.failUnlessReallyEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9)
5119 self.failUnlessReallyEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10)
5122 self.failUnlessEqual(stats["type"], "stats")
5124 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
5125 self.failUnlessReallyEqual(s["count-literal-files"], 1)
5126 self.failUnlessReallyEqual(s["count-directories"], 1)
5127 d.addCallback(_done)
5129 d.addErrback(self.explain_web_error)
5132 def _count_leases(self, ignored, which):
5133 u = self.uris[which]
5134 shares = self.find_uri_shares(u)
5136 for shnum, serverid, fn in shares:
5137 sf = get_share_file(fn)
5138 num_leases = len(list(sf.get_leases()))
5139 lease_counts.append( (fn, num_leases) )
5142 def _assert_leasecount(self, lease_counts, expected):
5143 for (fn, num_leases) in lease_counts:
5144 if num_leases != expected:
5145 self.fail("expected %d leases, have %d, on %s" %
5146 (expected, num_leases, fn))
5148 def test_add_lease(self):
5149 self.basedir = "web/Grid/add_lease"
5150 self.set_up_grid(num_clients=2)
5151 c0 = self.g.clients[0]
5154 d = c0.upload(upload.Data(DATA, convergence=""))
5155 def _stash_uri(ur, which):
5156 self.uris[which] = ur.get_uri()
5157 d.addCallback(_stash_uri, "one")
5158 d.addCallback(lambda ign:
5159 c0.upload(upload.Data(DATA+"1", convergence="")))
5160 d.addCallback(_stash_uri, "two")
5161 def _stash_mutable_uri(n, which):
5162 self.uris[which] = n.get_uri()
5163 assert isinstance(self.uris[which], str)
5164 d.addCallback(lambda ign:
5165 c0.create_mutable_file(publish.MutableData(DATA+"2")))
5166 d.addCallback(_stash_mutable_uri, "mutable")
5168 def _compute_fileurls(ignored):
5170 for which in self.uris:
5171 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
5172 d.addCallback(_compute_fileurls)
5174 d.addCallback(self._count_leases, "one")
5175 d.addCallback(self._assert_leasecount, 1)
5176 d.addCallback(self._count_leases, "two")
5177 d.addCallback(self._assert_leasecount, 1)
5178 d.addCallback(self._count_leases, "mutable")
5179 d.addCallback(self._assert_leasecount, 1)
5181 d.addCallback(self.CHECK, "one", "t=check") # no add-lease
5182 def _got_html_good(res):
5183 self.failUnlessIn("Healthy", res)
5184 self.failIfIn("Not Healthy", res)
5185 d.addCallback(_got_html_good)
5187 d.addCallback(self._count_leases, "one")
5188 d.addCallback(self._assert_leasecount, 1)
5189 d.addCallback(self._count_leases, "two")
5190 d.addCallback(self._assert_leasecount, 1)
5191 d.addCallback(self._count_leases, "mutable")
5192 d.addCallback(self._assert_leasecount, 1)
5194 # this CHECK uses the original client, which uses the same
5195 # lease-secrets, so it will just renew the original lease
5196 d.addCallback(self.CHECK, "one", "t=check&add-lease=true")
5197 d.addCallback(_got_html_good)
5199 d.addCallback(self._count_leases, "one")
5200 d.addCallback(self._assert_leasecount, 1)
5201 d.addCallback(self._count_leases, "two")
5202 d.addCallback(self._assert_leasecount, 1)
5203 d.addCallback(self._count_leases, "mutable")
5204 d.addCallback(self._assert_leasecount, 1)
5206 # this CHECK uses an alternate client, which adds a second lease
5207 d.addCallback(self.CHECK, "one", "t=check&add-lease=true", clientnum=1)
5208 d.addCallback(_got_html_good)
5210 d.addCallback(self._count_leases, "one")
5211 d.addCallback(self._assert_leasecount, 2)
5212 d.addCallback(self._count_leases, "two")
5213 d.addCallback(self._assert_leasecount, 1)
5214 d.addCallback(self._count_leases, "mutable")
5215 d.addCallback(self._assert_leasecount, 1)
5217 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true")
5218 d.addCallback(_got_html_good)
5220 d.addCallback(self._count_leases, "one")
5221 d.addCallback(self._assert_leasecount, 2)
5222 d.addCallback(self._count_leases, "two")
5223 d.addCallback(self._assert_leasecount, 1)
5224 d.addCallback(self._count_leases, "mutable")
5225 d.addCallback(self._assert_leasecount, 1)
5227 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true",
5229 d.addCallback(_got_html_good)
5231 d.addCallback(self._count_leases, "one")
5232 d.addCallback(self._assert_leasecount, 2)
5233 d.addCallback(self._count_leases, "two")
5234 d.addCallback(self._assert_leasecount, 1)
5235 d.addCallback(self._count_leases, "mutable")
5236 d.addCallback(self._assert_leasecount, 2)
5238 d.addErrback(self.explain_web_error)
5241 def test_deep_add_lease(self):
5242 self.basedir = "web/Grid/deep_add_lease"
5243 self.set_up_grid(num_clients=2)
5244 c0 = self.g.clients[0]
5248 d = c0.create_dirnode()
5249 def _stash_root_and_create_file(n):
5251 self.uris["root"] = n.get_uri()
5252 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
5253 return n.add_file(u"one", upload.Data(DATA, convergence=""))
5254 d.addCallback(_stash_root_and_create_file)
5255 def _stash_uri(fn, which):
5256 self.uris[which] = fn.get_uri()
5257 d.addCallback(_stash_uri, "one")
5258 d.addCallback(lambda ign:
5259 self.rootnode.add_file(u"small",
5260 upload.Data("literal",
5262 d.addCallback(_stash_uri, "small")
5264 d.addCallback(lambda ign:
5265 c0.create_mutable_file(publish.MutableData("mutable")))
5266 d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
5267 d.addCallback(_stash_uri, "mutable")
5269 d.addCallback(self.CHECK, "root", "t=stream-deep-check") # no add-lease
5271 units = [simplejson.loads(line)
5272 for line in res.splitlines()
5274 # root, one, small, mutable, stats
5275 self.failUnlessReallyEqual(len(units), 4+1)
5276 d.addCallback(_done)
5278 d.addCallback(self._count_leases, "root")
5279 d.addCallback(self._assert_leasecount, 1)
5280 d.addCallback(self._count_leases, "one")
5281 d.addCallback(self._assert_leasecount, 1)
5282 d.addCallback(self._count_leases, "mutable")
5283 d.addCallback(self._assert_leasecount, 1)
5285 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true")
5286 d.addCallback(_done)
5288 d.addCallback(self._count_leases, "root")
5289 d.addCallback(self._assert_leasecount, 1)
5290 d.addCallback(self._count_leases, "one")
5291 d.addCallback(self._assert_leasecount, 1)
5292 d.addCallback(self._count_leases, "mutable")
5293 d.addCallback(self._assert_leasecount, 1)
5295 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true",
5297 d.addCallback(_done)
5299 d.addCallback(self._count_leases, "root")
5300 d.addCallback(self._assert_leasecount, 2)
5301 d.addCallback(self._count_leases, "one")
5302 d.addCallback(self._assert_leasecount, 2)
5303 d.addCallback(self._count_leases, "mutable")
5304 d.addCallback(self._assert_leasecount, 2)
5306 d.addErrback(self.explain_web_error)
5310 def test_exceptions(self):
5311 self.basedir = "web/Grid/exceptions"
5312 self.set_up_grid(num_clients=1, num_servers=2)
5313 c0 = self.g.clients[0]
5314 c0.DEFAULT_ENCODING_PARAMETERS['happy'] = 2
5317 d = c0.create_dirnode()
5319 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
5320 self.fileurls["imaginary"] = self.fileurls["root"] + "imaginary"
5322 d.addCallback(_stash_root)
5323 d.addCallback(lambda ign: c0.upload(upload.Data(DATA, convergence="")))
5325 self.fileurls["1share"] = "uri/" + urllib.quote(ur.get_uri())
5326 self.delete_shares_numbered(ur.get_uri(), range(1,10))
5328 u = uri.from_string(ur.get_uri())
5329 u.key = testutil.flip_bit(u.key, 0)
5330 baduri = u.to_string()
5331 self.fileurls["0shares"] = "uri/" + urllib.quote(baduri)
5332 d.addCallback(_stash_bad)
5333 d.addCallback(lambda ign: c0.create_dirnode())
5334 def _mangle_dirnode_1share(n):
5336 url = self.fileurls["dir-1share"] = "uri/" + urllib.quote(u) + "/"
5337 self.fileurls["dir-1share-json"] = url + "?t=json"
5338 self.delete_shares_numbered(u, range(1,10))
5339 d.addCallback(_mangle_dirnode_1share)
5340 d.addCallback(lambda ign: c0.create_dirnode())
5341 def _mangle_dirnode_0share(n):
5343 url = self.fileurls["dir-0share"] = "uri/" + urllib.quote(u) + "/"
5344 self.fileurls["dir-0share-json"] = url + "?t=json"
5345 self.delete_shares_numbered(u, range(0,10))
5346 d.addCallback(_mangle_dirnode_0share)
5348 # NotEnoughSharesError should be reported sensibly, with a
5349 # text/plain explanation of the problem, and perhaps some
5350 # information on which shares *could* be found.
5352 d.addCallback(lambda ignored:
5353 self.shouldHTTPError("GET unrecoverable",
5354 410, "Gone", "NoSharesError",
5355 self.GET, self.fileurls["0shares"]))
5356 def _check_zero_shares(body):
5357 self.failIfIn("<html>", body)
5358 body = " ".join(body.strip().split())
5359 exp = ("NoSharesError: no shares could be found. "
5360 "Zero shares usually indicates a corrupt URI, or that "
5361 "no servers were connected, but it might also indicate "
5362 "severe corruption. You should perform a filecheck on "
5363 "this object to learn more. The full error message is: "
5364 "no shares (need 3). Last failure: None")
5365 self.failUnlessReallyEqual(exp, body)
5366 d.addCallback(_check_zero_shares)
5369 d.addCallback(lambda ignored:
5370 self.shouldHTTPError("GET 1share",
5371 410, "Gone", "NotEnoughSharesError",
5372 self.GET, self.fileurls["1share"]))
5373 def _check_one_share(body):
5374 self.failIfIn("<html>", body)
5375 body = " ".join(body.strip().split())
5376 msgbase = ("NotEnoughSharesError: This indicates that some "
5377 "servers were unavailable, or that shares have been "
5378 "lost to server departure, hard drive failure, or disk "
5379 "corruption. You should perform a filecheck on "
5380 "this object to learn more. The full error message is:"
5382 msg1 = msgbase + (" ran out of shares:"
5385 " overdue= unused= need 3. Last failure: None")
5386 msg2 = msgbase + (" ran out of shares:"
5388 " pending=Share(sh0-on-xgru5)"
5389 " overdue= unused= need 3. Last failure: None")
5390 self.failUnless(body == msg1 or body == msg2, body)
5391 d.addCallback(_check_one_share)
5393 d.addCallback(lambda ignored:
5394 self.shouldHTTPError("GET imaginary",
5395 404, "Not Found", None,
5396 self.GET, self.fileurls["imaginary"]))
5397 def _missing_child(body):
5398 self.failUnlessIn("No such child: imaginary", body)
5399 d.addCallback(_missing_child)
5401 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-0share"]))
5402 def _check_0shares_dir_html(body):
5403 self.failUnlessIn("<html>", body)
5404 # we should see the regular page, but without the child table or
5406 body = " ".join(body.strip().split())
5407 self.failUnlessIn('href="?t=info">More info on this directory',
5409 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5410 "could not be retrieved, because there were insufficient "
5411 "good shares. This might indicate that no servers were "
5412 "connected, insufficient servers were connected, the URI "
5413 "was corrupt, or that shares have been lost due to server "
5414 "departure, hard drive failure, or disk corruption. You "
5415 "should perform a filecheck on this object to learn more.")
5416 self.failUnlessIn(exp, body)
5417 self.failUnlessIn("No upload forms: directory is unreadable", body)
5418 d.addCallback(_check_0shares_dir_html)
5420 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-1share"]))
5421 def _check_1shares_dir_html(body):
5422 # at some point, we'll split UnrecoverableFileError into 0-shares
5423 # and some-shares like we did for immutable files (since there
5424 # are different sorts of advice to offer in each case). For now,
5425 # they present the same way.
5426 self.failUnlessIn("<html>", body)
5427 body = " ".join(body.strip().split())
5428 self.failUnlessIn('href="?t=info">More info on this directory',
5430 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5431 "could not be retrieved, because there were insufficient "
5432 "good shares. This might indicate that no servers were "
5433 "connected, insufficient servers were connected, the URI "
5434 "was corrupt, or that shares have been lost due to server "
5435 "departure, hard drive failure, or disk corruption. You "
5436 "should perform a filecheck on this object to learn more.")
5437 self.failUnlessIn(exp, body)
5438 self.failUnlessIn("No upload forms: directory is unreadable", body)
5439 d.addCallback(_check_1shares_dir_html)
5441 d.addCallback(lambda ignored:
5442 self.shouldHTTPError("GET dir-0share-json",
5443 410, "Gone", "UnrecoverableFileError",
5445 self.fileurls["dir-0share-json"]))
5446 def _check_unrecoverable_file(body):
5447 self.failIfIn("<html>", body)
5448 body = " ".join(body.strip().split())
5449 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5450 "could not be retrieved, because there were insufficient "
5451 "good shares. This might indicate that no servers were "
5452 "connected, insufficient servers were connected, the URI "
5453 "was corrupt, or that shares have been lost due to server "
5454 "departure, hard drive failure, or disk corruption. You "
5455 "should perform a filecheck on this object to learn more.")
5456 self.failUnlessReallyEqual(exp, body)
5457 d.addCallback(_check_unrecoverable_file)
5459 d.addCallback(lambda ignored:
5460 self.shouldHTTPError("GET dir-1share-json",
5461 410, "Gone", "UnrecoverableFileError",
5463 self.fileurls["dir-1share-json"]))
5464 d.addCallback(_check_unrecoverable_file)
5466 d.addCallback(lambda ignored:
5467 self.shouldHTTPError("GET imaginary",
5468 404, "Not Found", None,
5469 self.GET, self.fileurls["imaginary"]))
5471 # attach a webapi child that throws a random error, to test how it
5473 w = c0.getServiceNamed("webish")
5474 w.root.putChild("ERRORBOOM", ErrorBoom())
5476 # "Accept: */*" : should get a text/html stack trace
5477 # "Accept: text/plain" : should get a text/plain stack trace
5478 # "Accept: text/plain, application/octet-stream" : text/plain (CLI)
5479 # no Accept header: should get a text/html stack trace
5481 d.addCallback(lambda ignored:
5482 self.shouldHTTPError("GET errorboom_html",
5483 500, "Internal Server Error", None,
5484 self.GET, "ERRORBOOM",
5485 headers={"accept": "*/*"}))
5486 def _internal_error_html1(body):
5487 self.failUnlessIn("<html>", "expected HTML, not '%s'" % body)
5488 d.addCallback(_internal_error_html1)
5490 d.addCallback(lambda ignored:
5491 self.shouldHTTPError("GET errorboom_text",
5492 500, "Internal Server Error", None,
5493 self.GET, "ERRORBOOM",
5494 headers={"accept": "text/plain"}))
5495 def _internal_error_text2(body):
5496 self.failIfIn("<html>", body)
5497 self.failUnless(body.startswith("Traceback "), body)
5498 d.addCallback(_internal_error_text2)
5500 CLI_accepts = "text/plain, application/octet-stream"
5501 d.addCallback(lambda ignored:
5502 self.shouldHTTPError("GET errorboom_text",
5503 500, "Internal Server Error", None,
5504 self.GET, "ERRORBOOM",
5505 headers={"accept": CLI_accepts}))
5506 def _internal_error_text3(body):
5507 self.failIfIn("<html>", body)
5508 self.failUnless(body.startswith("Traceback "), body)
5509 d.addCallback(_internal_error_text3)
5511 d.addCallback(lambda ignored:
5512 self.shouldHTTPError("GET errorboom_text",
5513 500, "Internal Server Error", None,
5514 self.GET, "ERRORBOOM"))
5515 def _internal_error_html4(body):
5516 self.failUnlessIn("<html>", body)
5517 d.addCallback(_internal_error_html4)
5519 def _flush_errors(res):
5520 # Trial: please ignore the CompletelyUnhandledError in the logs
5521 self.flushLoggedErrors(CompletelyUnhandledError)
5523 d.addBoth(_flush_errors)
5527 def test_blacklist(self):
5528 # download from a blacklisted URI, get an error
5529 self.basedir = "web/Grid/blacklist"
5531 c0 = self.g.clients[0]
5532 c0_basedir = c0.basedir
5533 fn = os.path.join(c0_basedir, "access.blacklist")
5535 DATA = "off-limits " * 50
5537 d = c0.upload(upload.Data(DATA, convergence=""))
5538 def _stash_uri_and_create_dir(ur):
5539 self.uri = ur.get_uri()
5540 self.url = "uri/"+self.uri
5541 u = uri.from_string_filenode(self.uri)
5542 self.si = u.get_storage_index()
5543 childnode = c0.create_node_from_uri(self.uri, None)
5544 return c0.create_dirnode({u"blacklisted.txt": (childnode,{}) })
5545 d.addCallback(_stash_uri_and_create_dir)
5546 def _stash_dir(node):
5547 self.dir_node = node
5548 self.dir_uri = node.get_uri()
5549 self.dir_url = "uri/"+self.dir_uri
5550 d.addCallback(_stash_dir)
5551 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5552 def _check_dir_html(body):
5553 self.failUnlessIn("<html>", body)
5554 self.failUnlessIn("blacklisted.txt</a>", body)
5555 d.addCallback(_check_dir_html)
5556 d.addCallback(lambda ign: self.GET(self.url))
5557 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5559 def _blacklist(ign):
5561 f.write(" # this is a comment\n")
5563 f.write("\n") # also exercise blank lines
5564 f.write("%s %s\n" % (base32.b2a(self.si), "off-limits to you"))
5566 # clients should be checking the blacklist each time, so we don't
5567 # need to restart the client
5568 d.addCallback(_blacklist)
5569 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_uri",
5571 "Access Prohibited: off-limits",
5572 self.GET, self.url))
5574 # We should still be able to list the parent directory, in HTML...
5575 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5576 def _check_dir_html2(body):
5577 self.failUnlessIn("<html>", body)
5578 self.failUnlessIn("blacklisted.txt</strike>", body)
5579 d.addCallback(_check_dir_html2)
5581 # ... and in JSON (used by CLI).
5582 d.addCallback(lambda ign: self.GET(self.dir_url+"?t=json", followRedirect=True))
5583 def _check_dir_json(res):
5584 data = simplejson.loads(res)
5585 self.failUnless(isinstance(data, list), data)
5586 self.failUnlessEqual(data[0], "dirnode")
5587 self.failUnless(isinstance(data[1], dict), data)
5588 self.failUnlessIn("children", data[1])
5589 self.failUnlessIn("blacklisted.txt", data[1]["children"])
5590 childdata = data[1]["children"]["blacklisted.txt"]
5591 self.failUnless(isinstance(childdata, list), data)
5592 self.failUnlessEqual(childdata[0], "filenode")
5593 self.failUnless(isinstance(childdata[1], dict), data)
5594 d.addCallback(_check_dir_json)
5596 def _unblacklist(ign):
5597 open(fn, "w").close()
5598 # the Blacklist object watches mtime to tell when the file has
5599 # changed, but on windows this test will run faster than the
5600 # filesystem's mtime resolution. So we edit Blacklist.last_mtime
5601 # to force a reload.
5602 self.g.clients[0].blacklist.last_mtime -= 2.0
5603 d.addCallback(_unblacklist)
5605 # now a read should work
5606 d.addCallback(lambda ign: self.GET(self.url))
5607 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5609 # read again to exercise the blacklist-is-unchanged logic
5610 d.addCallback(lambda ign: self.GET(self.url))
5611 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5613 # now add a blacklisted directory, and make sure files under it are
5616 childnode = c0.create_node_from_uri(self.uri, None)
5617 return c0.create_dirnode({u"child": (childnode,{}) })
5618 d.addCallback(_add_dir)
5619 def _get_dircap(dn):
5620 self.dir_si_b32 = base32.b2a(dn.get_storage_index())
5621 self.dir_url_base = "uri/"+dn.get_write_uri()
5622 self.dir_url_json1 = "uri/"+dn.get_write_uri()+"?t=json"
5623 self.dir_url_json2 = "uri/"+dn.get_write_uri()+"/?t=json"
5624 self.dir_url_json_ro = "uri/"+dn.get_readonly_uri()+"/?t=json"
5625 self.child_url = "uri/"+dn.get_readonly_uri()+"/child"
5626 d.addCallback(_get_dircap)
5627 d.addCallback(lambda ign: self.GET(self.dir_url_base, followRedirect=True))
5628 d.addCallback(lambda body: self.failUnlessIn("<html>", body))
5629 d.addCallback(lambda ign: self.GET(self.dir_url_json1))
5630 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5631 d.addCallback(lambda ign: self.GET(self.dir_url_json2))
5632 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5633 d.addCallback(lambda ign: self.GET(self.dir_url_json_ro))
5634 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5635 d.addCallback(lambda ign: self.GET(self.child_url))
5636 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5638 def _block_dir(ign):
5640 f.write("%s %s\n" % (self.dir_si_b32, "dir-off-limits to you"))
5642 self.g.clients[0].blacklist.last_mtime -= 2.0
5643 d.addCallback(_block_dir)
5644 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir base",
5646 "Access Prohibited: dir-off-limits",
5647 self.GET, self.dir_url_base))
5648 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json1",
5650 "Access Prohibited: dir-off-limits",
5651 self.GET, self.dir_url_json1))
5652 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json2",
5654 "Access Prohibited: dir-off-limits",
5655 self.GET, self.dir_url_json2))
5656 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json_ro",
5658 "Access Prohibited: dir-off-limits",
5659 self.GET, self.dir_url_json_ro))
5660 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir child",
5662 "Access Prohibited: dir-off-limits",
5663 self.GET, self.child_url))
5667 class CompletelyUnhandledError(Exception):
5669 class ErrorBoom(rend.Page):
5670 def beforeRender(self, ctx):
5671 raise CompletelyUnhandledError("whoops")