1 import re, os.path, urllib
3 from twisted.application import service
4 from twisted.trial import unittest
5 from twisted.internet import defer
6 from twisted.web import client, error, http
7 from twisted.python import failure, log
8 from allmydata import interfaces, provisioning, uri, webish, upload, download
9 from allmydata.web import status
10 from allmydata.util import fileutil
11 from allmydata.test.common import FakeDirectoryNode, FakeCHKFileNode, FakeMutableFileNode, create_chk_filenode
12 from allmydata.interfaces import IURI, INewDirectoryURI, IReadonlyNewDirectoryURI, IFileURI, IMutableFileURI, IMutableFileNode
14 # create a fake uploader/downloader, and a couple of fake dirnodes, then
15 # create a webserver that works against them
17 class FakeIntroducerClient:
18 def get_all_connectors(self):
20 def get_all_connections_for(self, service_name):
22 def get_all_peerids(self):
25 class FakeClient(service.MultiService):
26 nodeid = "fake_nodeid"
27 basedir = "fake_basedir"
28 def get_versions(self):
29 return {'allmydata': "fake",
34 introducer_furl = "None"
35 introducer_client = FakeIntroducerClient()
36 _all_upload_status = [upload.UploadStatus()]
37 _all_download_status = [download.DownloadStatus()]
38 convergence = "some random string"
40 def connected_to_introducer(self):
43 def create_node_from_uri(self, auri):
44 u = uri.from_string(auri)
45 if (INewDirectoryURI.providedBy(u)
46 or IReadonlyNewDirectoryURI.providedBy(u)):
47 return FakeDirectoryNode(self).init_from_uri(u)
48 if IFileURI.providedBy(u):
49 return FakeCHKFileNode(u, self)
50 assert IMutableFileURI.providedBy(u), u
51 return FakeMutableFileNode(self).init_from_uri(u)
53 def create_empty_dirnode(self):
54 n = FakeDirectoryNode(self)
56 d.addCallback(lambda res: n)
59 def create_mutable_file(self, contents=""):
60 n = FakeMutableFileNode(self)
61 return n.create(contents)
63 def upload(self, uploadable):
64 d = uploadable.get_size()
65 d.addCallback(lambda size: uploadable.read(size))
68 n = create_chk_filenode(self, data)
69 results = upload.UploadResults()
70 results.uri = n.get_uri()
72 d.addCallback(_got_data)
75 def list_all_uploads(self):
77 def list_all_downloads(self):
80 def list_active_uploads(self):
81 return self._all_upload_status
82 def list_active_downloads(self):
83 return self._all_download_status
84 def list_active_publish(self):
86 def list_active_retrieve(self):
88 def list_active_mapupdate(self):
90 def list_recent_mapupdate(self):
93 def list_recent_uploads(self):
94 return self._all_upload_status
95 def list_recent_downloads(self):
96 return self._all_download_status
97 def list_recent_publish(self):
99 def list_recent_retrieve(self):
101 def list_active_helper_statuses(self):
103 def list_recent_helper_statuses(self):
106 class WebMixin(object):
108 self.s = FakeClient()
109 self.s.startService()
110 self.ws = s = webish.WebishServer("0")
111 s.allow_local_access(True)
112 s.setServiceParent(self.s)
113 port = s.listener._port.getHost().port
114 self.webish_url = "http://localhost:%d" % port
116 l = [ self.s.create_empty_dirnode() for x in range(6) ]
117 d = defer.DeferredList(l)
119 self.public_root = res[0][1]
120 assert interfaces.IDirectoryNode.providedBy(self.public_root), res
121 self.public_url = "/uri/" + self.public_root.get_uri()
122 self.private_root = res[1][1]
126 self._foo_uri = foo.get_uri()
127 self._foo_readonly_uri = foo.get_readonly_uri()
128 # NOTE: we ignore the deferred on all set_uri() calls, because we
129 # know the fake nodes do these synchronously
130 self.public_root.set_uri(u"foo", foo.get_uri())
132 self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
133 foo.set_uri(u"bar.txt", self._bar_txt_uri)
135 foo.set_uri(u"empty", res[3][1].get_uri())
136 sub_uri = res[4][1].get_uri()
137 foo.set_uri(u"sub", sub_uri)
138 sub = self.s.create_node_from_uri(sub_uri)
140 _ign, n, blocking_uri = self.makefile(1)
141 foo.set_uri(u"blockingfile", blocking_uri)
143 unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
144 # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
145 # still think of it as an umlaut
146 foo.set_uri(unicode_filename, self._bar_txt_uri)
148 _ign, n, baz_file = self.makefile(2)
149 sub.set_uri(u"baz.txt", baz_file)
151 _ign, n, self._bad_file_uri = self.makefile(3)
152 # this uri should not be downloadable
153 del FakeCHKFileNode.all_contents[self._bad_file_uri]
156 self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri())
157 rodir.set_uri(u"nor", baz_file)
162 # public/foo/blockingfile
165 # public/foo/sub/baz.txt
167 # public/reedownlee/nor
168 self.NEWFILE_CONTENTS = "newfile contents\n"
170 return foo.get_metadata_for(u"bar.txt")
172 def _got_metadata(metadata):
173 self._bar_txt_metadata = metadata
174 d.addCallback(_got_metadata)
177 def makefile(self, number):
178 contents = "contents of file %s\n" % number
179 n = create_chk_filenode(self.s, contents)
180 return contents, n, n.get_uri()
183 return self.s.stopService()
185 def failUnlessIsBarDotTxt(self, res):
186 self.failUnlessEqual(res, self.BAR_CONTENTS, res)
188 def failUnlessIsBarJSON(self, res):
189 data = simplejson.loads(res)
190 self.failUnless(isinstance(data, list))
191 self.failUnlessEqual(data[0], "filenode")
192 self.failUnless(isinstance(data[1], dict))
193 self.failIf("rw_uri" in data[1]) # immutable
194 self.failUnlessEqual(data[1]["ro_uri"], self._bar_txt_uri)
195 self.failUnlessEqual(data[1]["size"], len(self.BAR_CONTENTS))
197 def failUnlessIsFooJSON(self, res):
198 data = simplejson.loads(res)
199 self.failUnless(isinstance(data, list))
200 self.failUnlessEqual(data[0], "dirnode", res)
201 self.failUnless(isinstance(data[1], dict))
202 self.failUnless("rw_uri" in data[1]) # mutable
203 self.failUnlessEqual(data[1]["rw_uri"], self._foo_uri)
204 self.failUnlessEqual(data[1]["ro_uri"], self._foo_readonly_uri)
206 kidnames = sorted(data[1]["children"])
207 self.failUnlessEqual(kidnames,
208 [u"bar.txt", u"blockingfile", u"empty",
209 u"n\u00fc.txt", u"sub"])
210 kids = data[1]["children"]
211 self.failUnlessEqual(kids[u"sub"][0], "dirnode")
212 self.failUnless("metadata" in kids[u"sub"][1])
213 self.failUnless("ctime" in kids[u"sub"][1]["metadata"])
214 self.failUnless("mtime" in kids[u"sub"][1]["metadata"])
215 self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
216 self.failUnlessEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
217 self.failUnlessEqual(kids[u"bar.txt"][1]["ro_uri"], self._bar_txt_uri)
218 self.failUnlessEqual(kids[u"bar.txt"][1]["metadata"]["ctime"],
219 self._bar_txt_metadata["ctime"])
220 self.failUnlessEqual(kids[u"n\u00fc.txt"][1]["ro_uri"],
223 def GET(self, urlpath, followRedirect=False):
224 url = self.webish_url + urlpath
225 return client.getPage(url, method="GET", followRedirect=followRedirect)
227 def PUT(self, urlpath, data):
228 url = self.webish_url + urlpath
229 return client.getPage(url, method="PUT", postdata=data)
231 def DELETE(self, urlpath):
232 url = self.webish_url + urlpath
233 return client.getPage(url, method="DELETE")
235 def POST(self, urlpath, followRedirect=False, **fields):
236 url = self.webish_url + urlpath
237 sepbase = "boogabooga"
241 form.append('Content-Disposition: form-data; name="_charset"')
245 for name, value in fields.iteritems():
246 if isinstance(value, tuple):
247 filename, value = value
248 form.append('Content-Disposition: form-data; name="%s"; '
249 'filename="%s"' % (name, filename.encode("utf-8")))
251 form.append('Content-Disposition: form-data; name="%s"' % name)
253 form.append(str(value))
256 body = "\r\n".join(form) + "\r\n"
257 headers = {"content-type": "multipart/form-data; boundary=%s" % sepbase,
259 return client.getPage(url, method="POST", postdata=body,
260 headers=headers, followRedirect=followRedirect)
262 def shouldFail(self, res, expected_failure, which,
263 substring=None, response_substring=None):
264 if isinstance(res, failure.Failure):
265 res.trap(expected_failure)
267 self.failUnless(substring in str(res),
268 "substring '%s' not in '%s'"
269 % (substring, str(res)))
270 if response_substring:
271 self.failUnless(response_substring in res.value.response,
272 "respose substring '%s' not in '%s'"
273 % (response_substring, res.value.response))
275 self.fail("%s was supposed to raise %s, not get '%s'" %
276 (which, expected_failure, res))
278 def shouldFail2(self, expected_failure, which, substring,
280 callable, *args, **kwargs):
281 assert substring is None or isinstance(substring, str)
282 assert response_substring is None or isinstance(response_substring, str)
283 d = defer.maybeDeferred(callable, *args, **kwargs)
285 if isinstance(res, failure.Failure):
286 res.trap(expected_failure)
288 self.failUnless(substring in str(res),
289 "substring '%s' not in '%s'"
290 % (substring, str(res)))
291 if response_substring:
292 self.failUnless(response_substring in res.value.response,
293 "respose substring '%s' not in '%s'"
294 % (response_substring, res.value.response))
296 self.fail("%s was supposed to raise %s, not get '%s'" %
297 (which, expected_failure, res))
301 def should404(self, res, which):
302 if isinstance(res, failure.Failure):
303 res.trap(error.Error)
304 self.failUnlessEqual(res.value.status, "404")
306 self.fail("%s was supposed to Error(404), not get '%s'" %
309 def shouldHTTPError(self, res, which, code=None, substring=None,
310 response_substring=None):
311 if isinstance(res, failure.Failure):
312 res.trap(error.Error)
314 self.failUnlessEqual(res.value.status, str(code))
316 self.failUnless(substring in str(res),
317 "substring '%s' not in '%s'"
318 % (substring, str(res)))
319 if response_substring:
320 self.failUnless(response_substring in res.value.response,
321 "respose substring '%s' not in '%s'"
322 % (response_substring, res.value.response))
324 self.fail("%s was supposed to Error(%s), not get '%s'" %
327 def shouldHTTPError2(self, which,
328 code=None, substring=None, response_substring=None,
329 callable=None, *args, **kwargs):
330 assert substring is None or isinstance(substring, str)
332 d = defer.maybeDeferred(callable, *args, **kwargs)
333 d.addBoth(self.shouldHTTPError, which,
334 code, substring, response_substring)
338 class Web(WebMixin, unittest.TestCase):
339 def test_create(self):
342 def test_welcome(self):
345 self.failUnless('Welcome To AllMyData' in res)
346 self.failUnless('Tahoe' in res)
348 self.s.basedir = 'web/test_welcome'
349 fileutil.make_dirs("web/test_welcome")
350 fileutil.make_dirs("web/test_welcome/private")
352 d.addCallback(_check)
355 def test_provisioning_math(self):
356 self.failUnlessEqual(provisioning.binomial(10, 0), 1)
357 self.failUnlessEqual(provisioning.binomial(10, 1), 10)
358 self.failUnlessEqual(provisioning.binomial(10, 2), 45)
359 self.failUnlessEqual(provisioning.binomial(10, 9), 10)
360 self.failUnlessEqual(provisioning.binomial(10, 10), 1)
362 def test_provisioning(self):
363 d = self.GET("/provisioning/")
365 self.failUnless('Tahoe Provisioning Tool' in res)
366 fields = {'filled': True,
367 "num_users": int(50e3),
368 "files_per_user": 1000,
369 "space_per_user": int(1e9),
370 "sharing_ratio": 1.0,
371 "encoding_parameters": "3-of-10-5",
373 "ownership_mode": "A",
374 "download_rate": 100,
379 return self.POST("/provisioning/", **fields)
381 d.addCallback(_check)
383 self.failUnless('Tahoe Provisioning Tool' in res)
384 self.failUnless("Share space consumed: 167.01TB" in res)
386 fields = {'filled': True,
387 "num_users": int(50e6),
388 "files_per_user": 1000,
389 "space_per_user": int(5e9),
390 "sharing_ratio": 1.0,
391 "encoding_parameters": "25-of-100-50",
392 "num_servers": 30000,
393 "ownership_mode": "E",
394 "drive_failure_model": "U",
396 "download_rate": 1000,
401 return self.POST("/provisioning/", **fields)
402 d.addCallback(_check2)
404 self.failUnless("Share space consumed: huge!" in res)
405 fields = {'filled': True}
406 return self.POST("/provisioning/", **fields)
407 d.addCallback(_check3)
409 self.failUnless("Share space consumed:" in res)
410 d.addCallback(_check4)
413 def test_status(self):
414 dl_num = self.s.list_recent_downloads()[0].get_counter()
415 ul_num = self.s.list_recent_uploads()[0].get_counter()
416 d = self.GET("/status", followRedirect=True)
418 self.failUnless('Upload and Download Status' in res, res)
419 self.failUnless('"down-%d"' % dl_num in res, res)
420 self.failUnless('"up-%d"' % ul_num in res, res)
421 d.addCallback(_check)
422 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
424 self.failUnless("File Download Status" in res, res)
425 d.addCallback(_check_dl)
426 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
428 self.failUnless("File Upload Status" in res, res)
429 d.addCallback(_check_ul)
432 def test_status_numbers(self):
433 drrm = status.DownloadResultsRendererMixin()
434 self.failUnlessEqual(drrm.render_time(None, None), "")
435 self.failUnlessEqual(drrm.render_time(None, 2.5), "2.50s")
436 self.failUnlessEqual(drrm.render_time(None, 0.25), "250ms")
437 self.failUnlessEqual(drrm.render_time(None, 0.0021), "2.1ms")
438 self.failUnlessEqual(drrm.render_time(None, 0.000123), "123us")
439 self.failUnlessEqual(drrm.render_rate(None, None), "")
440 self.failUnlessEqual(drrm.render_rate(None, 2500000), "2.50MBps")
441 self.failUnlessEqual(drrm.render_rate(None, 30100), "30.1kBps")
442 self.failUnlessEqual(drrm.render_rate(None, 123), "123Bps")
444 urrm = status.UploadResultsRendererMixin()
445 self.failUnlessEqual(urrm.render_time(None, None), "")
446 self.failUnlessEqual(urrm.render_time(None, 2.5), "2.50s")
447 self.failUnlessEqual(urrm.render_time(None, 0.25), "250ms")
448 self.failUnlessEqual(urrm.render_time(None, 0.0021), "2.1ms")
449 self.failUnlessEqual(urrm.render_time(None, 0.000123), "123us")
450 self.failUnlessEqual(urrm.render_rate(None, None), "")
451 self.failUnlessEqual(urrm.render_rate(None, 2500000), "2.50MBps")
452 self.failUnlessEqual(urrm.render_rate(None, 30100), "30.1kBps")
453 self.failUnlessEqual(urrm.render_rate(None, 123), "123Bps")
455 def test_GET_FILEURL(self):
456 d = self.GET(self.public_url + "/foo/bar.txt")
457 d.addCallback(self.failUnlessIsBarDotTxt)
460 def test_GET_FILEURL_save(self):
461 d = self.GET(self.public_url + "/foo/bar.txt?save=bar.txt")
462 # TODO: look at the headers, expect a Content-Disposition: attachment
464 d.addCallback(self.failUnlessIsBarDotTxt)
467 def test_GET_FILEURL_download(self):
468 d = self.GET(self.public_url + "/foo/bar.txt?t=download")
469 d.addCallback(self.failUnlessIsBarDotTxt)
472 def test_GET_FILEURL_missing(self):
473 d = self.GET(self.public_url + "/foo/missing")
474 d.addBoth(self.should404, "test_GET_FILEURL_missing")
477 def test_PUT_NEWFILEURL(self):
478 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
479 # TODO: we lose the response code, so we can't check this
480 #self.failUnlessEqual(responsecode, 201)
481 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
482 d.addCallback(lambda res:
483 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
484 self.NEWFILE_CONTENTS))
487 def test_PUT_NEWFILEURL_replace(self):
488 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
489 # TODO: we lose the response code, so we can't check this
490 #self.failUnlessEqual(responsecode, 200)
491 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
492 d.addCallback(lambda res:
493 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
494 self.NEWFILE_CONTENTS))
497 def test_PUT_NEWFILEURL_no_replace(self):
498 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
499 self.NEWFILE_CONTENTS)
500 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
502 "There was already a child by that name, and you asked me "
506 def test_PUT_NEWFILEURL_mkdirs(self):
507 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
509 d.addCallback(self.failUnlessURIMatchesChild, fn, u"newdir/new.txt")
510 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
511 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
512 d.addCallback(lambda res:
513 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
514 self.NEWFILE_CONTENTS))
517 def test_PUT_NEWFILEURL_blocked(self):
518 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
519 self.NEWFILE_CONTENTS)
520 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
522 "cannot create directory because there is a file in the way")
525 def test_DELETE_FILEURL(self):
526 d = self.DELETE(self.public_url + "/foo/bar.txt")
527 d.addCallback(lambda res:
528 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
531 def test_DELETE_FILEURL_missing(self):
532 d = self.DELETE(self.public_url + "/foo/missing")
533 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
536 def test_DELETE_FILEURL_missing2(self):
537 d = self.DELETE(self.public_url + "/missing/missing")
538 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
541 def test_GET_FILEURL_json(self):
542 # twisted.web.http.parse_qs ignores any query args without an '=', so
543 # I can't do "GET /path?json", I have to do "GET /path/t=json"
544 # instead. This may make it tricky to emulate the S3 interface
546 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
547 d.addCallback(self.failUnlessIsBarJSON)
550 def test_GET_FILEURL_json_missing(self):
551 d = self.GET(self.public_url + "/foo/missing?json")
552 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
555 def disable_local_access(self, res=None):
556 self.ws.allow_local_access(False)
559 def test_GET_FILEURL_localfile(self):
560 localfile = os.path.abspath("web/GET_FILEURL_local file")
561 url = (self.public_url + "/foo/bar.txt?t=download&localfile=%s" %
562 urllib.quote(localfile))
563 fileutil.make_dirs("web")
566 self.failUnless(os.path.exists(localfile))
567 data = open(localfile, "rb").read()
568 self.failUnlessEqual(data, self.BAR_CONTENTS)
572 def test_GET_FILEURL_localfile_disabled(self):
573 localfile = os.path.abspath("web/GET_FILEURL_local file_disabled")
574 url = (self.public_url + "/foo/bar.txt?t=download&localfile=%s" %
575 urllib.quote(localfile))
576 fileutil.make_dirs("web")
577 self.disable_local_access()
579 d.addBoth(self.shouldFail, error.Error, "localfile disabled",
581 "local file access is disabled")
584 def test_GET_FILEURL_localfile_nonlocal(self):
585 # TODO: somehow pretend that we aren't local, and verify that the
586 # server refuses to write to local files, probably by changing the
587 # server's idea of what counts as "local".
588 old_LOCALHOST = webish.LOCALHOST
589 webish.LOCALHOST = "127.0.0.2"
590 localfile = os.path.abspath("web/GET_FILEURL_local file_nonlocal")
591 fileutil.make_dirs("web")
592 d = self.GET(self.public_url + "/foo/bar.txt?t=download&localfile=%s"
593 % urllib.quote(localfile))
594 d.addBoth(self.shouldFail, error.Error, "localfile non-local",
596 "localfile= or localdir= requires a local connection")
598 self.failIf(os.path.exists(localfile))
599 d.addCallback(_check)
601 webish.LOCALHOST = old_LOCALHOST
606 def test_GET_FILEURL_localfile_nonabsolute(self):
607 localfile = "web/nonabsolute/path"
608 fileutil.make_dirs("web/nonabsolute")
609 d = self.GET(self.public_url + "/foo/bar.txt?t=download&localfile=%s"
610 % urllib.quote(localfile))
611 d.addBoth(self.shouldFail, error.Error, "localfile non-absolute",
613 "localfile= or localdir= requires an absolute path")
615 self.failIf(os.path.exists(localfile))
616 d.addCallback(_check)
619 def test_PUT_NEWFILEURL_localfile(self):
620 localfile = os.path.abspath("web/PUT_NEWFILEURL_local file")
621 url = (self.public_url + "/foo/new.txt?t=upload&localfile=%s" %
622 urllib.quote(localfile))
623 fileutil.make_dirs("web")
624 f = open(localfile, "wb")
625 f.write(self.NEWFILE_CONTENTS)
627 d = self.PUT(url, "")
628 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
629 d.addCallback(lambda res:
630 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
631 self.NEWFILE_CONTENTS))
634 def test_PUT_NEWFILEURL_localfile_missingarg(self):
635 url = self.public_url + "/foo/new.txt?t=upload"
636 d = self.shouldHTTPError2("test_PUT_NEWFILEURL_localfile_missing",
638 "t=upload requires localfile= or localdir=",
642 def test_PUT_NEWFILEURL_localfile_disabled(self):
643 localfile = os.path.abspath("web/PUT_NEWFILEURL_local file_disabled")
644 url = (self.public_url + "/foo/new.txt?t=upload&localfile=%s" %
645 urllib.quote(localfile))
646 fileutil.make_dirs("web")
647 f = open(localfile, "wb")
648 f.write(self.NEWFILE_CONTENTS)
650 self.disable_local_access()
651 d = self.PUT(url, "")
652 d.addBoth(self.shouldFail, error.Error, "put localfile disabled",
654 "local file access is disabled")
657 def test_PUT_NEWFILEURL_localfile_mkdirs(self):
658 localfile = os.path.abspath("web/PUT_NEWFILEURL_local file_mkdirs")
659 fileutil.make_dirs("web")
660 f = open(localfile, "wb")
661 f.write(self.NEWFILE_CONTENTS)
663 d = self.PUT(self.public_url + "/foo/newdir/new.txt?t=upload&localfile=%s"
664 % urllib.quote(localfile), "")
665 d.addCallback(self.failUnlessURIMatchesChild,
666 self._foo_node, u"newdir/new.txt")
667 d.addCallback(lambda res:
668 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
669 d.addCallback(lambda res:
670 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
671 d.addCallback(lambda res:
672 self.failUnlessChildContentsAre(self._foo_node,
674 self.NEWFILE_CONTENTS))
677 def test_GET_FILEURL_uri(self):
678 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
680 self.failUnlessEqual(res, self._bar_txt_uri)
681 d.addCallback(_check)
682 d.addCallback(lambda res:
683 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
685 # for now, for files, uris and readonly-uris are the same
686 self.failUnlessEqual(res, self._bar_txt_uri)
687 d.addCallback(_check2)
690 def test_GET_FILEURL_badtype(self):
691 d = self.shouldHTTPError2("GET t=bogus", 400, "Bad Request",
694 self.public_url + "/foo/bar.txt?t=bogus")
697 def test_GET_FILEURL_uri_missing(self):
698 d = self.GET(self.public_url + "/foo/missing?t=uri")
699 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
702 def test_GET_DIRURL(self):
703 # the addSlash means we get a redirect here
704 d = self.GET(self.public_url + "/foo", followRedirect=True)
706 # the FILE reference points to a URI, but it should end in bar.txt
707 self.failUnless(re.search(r'<td>'
708 '<a href="[^"]+bar.txt">bar.txt</a>'
711 '\s+<td>%d</td>' % len(self.BAR_CONTENTS)
713 # the DIR reference just points to a URI
714 self.failUnless(re.search(r'<td><a href="/uri/URI%3ADIR2%3A[^"]+">sub</a></td>'
715 '\s+<td>DIR</td>', res))
716 d.addCallback(_check)
718 # look at a directory which is readonly
719 d.addCallback(lambda res:
720 self.GET(self.public_url + "/reedownlee", followRedirect=True))
722 self.failUnless("(readonly)" in res, res)
723 self.failIf("Upload a file" in res, res)
724 d.addCallback(_check2)
726 # and at a directory that contains a readonly directory
727 d.addCallback(lambda res:
728 self.GET(self.public_url, followRedirect=True))
730 self.failUnless(re.search(r'<td><a href="/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a>'
731 '</td>\s+<td>DIR-RO</td>', res))
732 d.addCallback(_check3)
736 def test_GET_DIRURL_badtype(self):
737 d = self.shouldHTTPError2("test_GET_DIRURL_badtype",
741 self.public_url + "/foo?t=bogus")
744 def test_GET_DIRURL_json(self):
745 d = self.GET(self.public_url + "/foo?t=json")
746 d.addCallback(self.failUnlessIsFooJSON)
749 def test_GET_DIRURL_manifest(self):
750 d = self.GET(self.public_url + "/foo?t=manifest", followRedirect=True)
752 self.failUnless("Refresh Capabilities" in manifest)
756 def test_GET_DIRURL_deepsize(self):
757 d = self.GET(self.public_url + "/foo?t=deep-size", followRedirect=True)
759 self.failUnless(re.search(r'^\d+$', manifest), manifest)
760 self.failUnlessEqual("57", manifest)
764 def test_GET_DIRURL_uri(self):
765 d = self.GET(self.public_url + "/foo?t=uri")
767 self.failUnlessEqual(res, self._foo_uri)
768 d.addCallback(_check)
771 def test_GET_DIRURL_readonly_uri(self):
772 d = self.GET(self.public_url + "/foo?t=readonly-uri")
774 self.failUnlessEqual(res, self._foo_readonly_uri)
775 d.addCallback(_check)
778 def test_PUT_NEWDIRURL(self):
779 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
780 d.addCallback(lambda res:
781 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
782 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
783 d.addCallback(self.failUnlessNodeKeysAre, [])
786 def test_PUT_NEWDIRURL_replace(self):
787 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
788 d.addCallback(lambda res:
789 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
790 d.addCallback(lambda res: self._foo_node.get(u"sub"))
791 d.addCallback(self.failUnlessNodeKeysAre, [])
794 def test_PUT_NEWDIRURL_no_replace(self):
795 d = self.PUT(self.public_url + "/foo/sub?t=mkdir&replace=false", "")
796 d.addBoth(self.shouldFail, error.Error, "PUT_NEWDIRURL_no_replace",
798 "There was already a child by that name, and you asked me "
800 d.addCallback(lambda res:
801 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
802 d.addCallback(lambda res: self._foo_node.get(u"sub"))
803 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
806 def test_PUT_NEWDIRURL_mkdir_p(self):
807 d = defer.succeed(None)
808 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
809 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
810 d.addCallback(lambda res: self._foo_node.get(u"mkp"))
811 def mkdir_p(mkpnode):
812 url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
814 def made_subsub(ssuri):
815 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
816 d.addCallback(lambda ssnode: self.failUnlessEqual(ssnode.get_uri(), ssuri))
818 d.addCallback(lambda uri2: self.failUnlessEqual(uri2, ssuri))
820 d.addCallback(made_subsub)
822 d.addCallback(mkdir_p)
825 def test_PUT_NEWDIRURL_mkdirs(self):
826 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
827 d.addCallback(lambda res:
828 self.failIfNodeHasChild(self._foo_node, u"newdir"))
829 d.addCallback(lambda res:
830 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
831 d.addCallback(lambda res:
832 self._foo_node.get_child_at_path(u"subdir/newdir"))
833 d.addCallback(self.failUnlessNodeKeysAre, [])
836 def test_DELETE_DIRURL(self):
837 d = self.DELETE(self.public_url + "/foo")
838 d.addCallback(lambda res:
839 self.failIfNodeHasChild(self.public_root, u"foo"))
842 def test_DELETE_DIRURL_missing(self):
843 d = self.DELETE(self.public_url + "/foo/missing")
844 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
845 d.addCallback(lambda res:
846 self.failUnlessNodeHasChild(self.public_root, u"foo"))
849 def test_DELETE_DIRURL_missing2(self):
850 d = self.DELETE(self.public_url + "/missing")
851 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
854 def test_walker(self):
856 def _visitor(path, node, metadata):
857 out.append((path, node))
858 return defer.succeed(None)
859 w = webish.DirnodeWalkerMixin()
860 d = w.walk(self.public_root, _visitor)
862 names = [path for (path,node) in out]
863 self.failUnlessEqual(sorted(names),
866 (u'foo',u'blockingfile'),
868 (u'foo', u"n\u00fc.txt"),
870 (u'foo',u'sub',u'baz.txt'),
872 (u'reedownlee', u'nor'),
874 subindex = names.index( (u'foo', u'sub') )
875 bazindex = names.index( (u'foo', u'sub', u'baz.txt') )
876 self.failUnless(subindex < bazindex)
877 for path,node in out:
878 if path[-1] in (u'bar.txt', u"n\u00fc.txt", u'blockingfile',
880 self.failUnless(interfaces.IFileNode.providedBy(node))
882 self.failUnless(interfaces.IDirectoryNode.providedBy(node))
883 d.addCallback(_check)
886 def test_GET_DIRURL_localdir(self):
887 localdir = os.path.abspath("web/GET_DIRURL_local dir")
888 fileutil.make_dirs("web")
889 d = self.GET(self.public_url + "/foo?t=download&localdir=%s" %
890 urllib.quote(localdir))
892 barfile = os.path.join(localdir, "bar.txt")
893 self.failUnless(os.path.exists(barfile))
894 data = open(barfile, "rb").read()
895 self.failUnlessEqual(data, self.BAR_CONTENTS)
896 blockingfile = os.path.join(localdir, "blockingfile")
897 self.failUnless(os.path.exists(blockingfile))
898 subdir = os.path.join(localdir, "sub")
899 self.failUnless(os.path.isdir(subdir))
900 d.addCallback(_check)
903 def test_GET_DIRURL_localdir_disabled(self):
904 localdir = os.path.abspath("web/GET_DIRURL_local dir_disabled")
905 fileutil.make_dirs("web")
906 self.disable_local_access()
907 d = self.GET(self.public_url + "/foo?t=download&localdir=%s" %
908 urllib.quote(localdir))
909 d.addBoth(self.shouldFail, error.Error, "localfile disabled",
911 "local file access is disabled")
914 def test_GET_DIRURL_localdir_nonabsolute(self):
915 localdir = "web/nonabsolute/dir path"
916 fileutil.make_dirs("web/nonabsolute")
917 d = self.GET(self.public_url + "/foo?t=download&localdir=%s" %
918 urllib.quote(localdir))
919 d.addBoth(self.shouldFail, error.Error, "localdir non-absolute",
921 "localfile= or localdir= requires an absolute path")
923 self.failIf(os.path.exists(localdir))
924 d.addCallback(_check)
927 def test_GET_DIRURL_localdir_nolocaldir(self):
928 d = self.shouldHTTPError2("GET_DIRURL_localdir_nolocaldir",
930 "t=download requires localdir=",
932 self.public_url + "/foo?t=download")
935 def touch(self, localdir, filename):
936 path = os.path.join(localdir, filename)
938 f.write("contents of %s\n" % filename)
943 w = webish.DirnodeWalkerMixin()
944 def visitor(childpath, childnode, metadata):
946 d = w.walk(self.public_root, visitor)
949 def failUnlessNodeKeysAre(self, node, expected_keys):
950 for k in expected_keys:
951 assert isinstance(k, unicode)
953 def _check(children):
954 self.failUnlessEqual(sorted(children.keys()), sorted(expected_keys))
955 d.addCallback(_check)
957 def failUnlessNodeHasChild(self, node, name):
958 assert isinstance(name, unicode)
960 def _check(children):
961 self.failUnless(name in children)
962 d.addCallback(_check)
964 def failIfNodeHasChild(self, node, name):
965 assert isinstance(name, unicode)
967 def _check(children):
968 self.failIf(name in children)
969 d.addCallback(_check)
972 def failUnlessChildContentsAre(self, node, name, expected_contents):
973 assert isinstance(name, unicode)
974 d = node.get_child_at_path(name)
975 d.addCallback(lambda node: node.download_to_data())
976 def _check(contents):
977 self.failUnlessEqual(contents, expected_contents)
978 d.addCallback(_check)
981 def failUnlessChildURIIs(self, node, name, expected_uri):
982 assert isinstance(name, unicode)
983 d = node.get_child_at_path(name)
985 self.failUnlessEqual(child.get_uri(), expected_uri.strip())
986 d.addCallback(_check)
989 def failUnlessURIMatchesChild(self, got_uri, node, name):
990 assert isinstance(name, unicode)
991 d = node.get_child_at_path(name)
993 self.failUnlessEqual(got_uri.strip(), child.get_uri())
994 d.addCallback(_check)
997 def failUnlessCHKURIHasContents(self, got_uri, contents):
998 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
1000 def test_PUT_NEWDIRURL_localdir(self):
1001 localdir = os.path.abspath("web/PUT_NEWDIRURL_local dir")
1002 # create some files there
1003 fileutil.make_dirs(os.path.join(localdir, "one"))
1004 fileutil.make_dirs(os.path.join(localdir, "one/sub"))
1005 fileutil.make_dirs(os.path.join(localdir, "two"))
1006 fileutil.make_dirs(os.path.join(localdir, "three"))
1007 self.touch(localdir, "three/foo.txt")
1008 self.touch(localdir, "three/bar.txt")
1009 self.touch(localdir, "zap.zip")
1011 d = self.PUT(self.public_url + "/newdir?t=upload&localdir=%s"
1012 % urllib.quote(localdir), "")
1013 pr = self.public_root
1014 d.addCallback(lambda res: self.failUnlessNodeHasChild(pr, u"newdir"))
1015 d.addCallback(lambda res: pr.get(u"newdir"))
1016 d.addCallback(self.failUnlessNodeKeysAre,
1017 [u"one", u"two", u"three", u"zap.zip"])
1018 d.addCallback(lambda res: pr.get_child_at_path(u"newdir/one"))
1019 d.addCallback(self.failUnlessNodeKeysAre, [u"sub"])
1020 d.addCallback(lambda res: pr.get_child_at_path(u"newdir/three"))
1021 d.addCallback(self.failUnlessNodeKeysAre, [u"foo.txt", u"bar.txt"])
1022 d.addCallback(lambda res: pr.get_child_at_path(u"newdir/three/bar.txt"))
1023 d.addCallback(lambda barnode: barnode.download_to_data())
1024 d.addCallback(lambda contents:
1025 self.failUnlessEqual(contents,
1026 "contents of three/bar.txt\n"))
1029 def test_PUT_NEWDIRURL_localdir_disabled(self):
1030 localdir = os.path.abspath("web/PUT_NEWDIRURL_local dir_disabled")
1031 # create some files there
1032 fileutil.make_dirs(os.path.join(localdir, "one"))
1033 fileutil.make_dirs(os.path.join(localdir, "one/sub"))
1034 fileutil.make_dirs(os.path.join(localdir, "two"))
1035 fileutil.make_dirs(os.path.join(localdir, "three"))
1036 self.touch(localdir, "three/foo.txt")
1037 self.touch(localdir, "three/bar.txt")
1038 self.touch(localdir, "zap.zip")
1040 self.disable_local_access()
1041 d = self.PUT(self.public_url + "/newdir?t=upload&localdir=%s"
1042 % urllib.quote(localdir), "")
1043 d.addBoth(self.shouldFail, error.Error, "localfile disabled",
1045 "local file access is disabled")
1048 def test_PUT_NEWDIRURL_localdir_mkdirs(self):
1049 localdir = os.path.abspath("web/PUT_NEWDIRURL_local dir_mkdirs")
1050 # create some files there
1051 fileutil.make_dirs(os.path.join(localdir, "one"))
1052 fileutil.make_dirs(os.path.join(localdir, "one/sub"))
1053 fileutil.make_dirs(os.path.join(localdir, "two"))
1054 fileutil.make_dirs(os.path.join(localdir, "three"))
1055 self.touch(localdir, "three/foo.txt")
1056 self.touch(localdir, "three/bar.txt")
1057 self.touch(localdir, "zap.zip")
1059 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=upload&localdir=%s"
1060 % urllib.quote(localdir),
1063 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"subdir"))
1064 d.addCallback(lambda res: fn.get_child_at_path(u"subdir/newdir"))
1065 d.addCallback(self.failUnlessNodeKeysAre,
1066 [u"one", u"two", u"three", u"zap.zip"])
1067 d.addCallback(lambda res: fn.get_child_at_path(u"subdir/newdir/one"))
1068 d.addCallback(self.failUnlessNodeKeysAre, [u"sub"])
1069 d.addCallback(lambda res: fn.get_child_at_path(u"subdir/newdir/three"))
1070 d.addCallback(self.failUnlessNodeKeysAre, [u"foo.txt", u"bar.txt"])
1071 d.addCallback(lambda res:
1072 fn.get_child_at_path(u"subdir/newdir/three/bar.txt"))
1073 d.addCallback(lambda barnode: barnode.download_to_data())
1074 d.addCallback(lambda contents:
1075 self.failUnlessEqual(contents,
1076 "contents of three/bar.txt\n"))
1079 def test_PUT_NEWDIRURL_localdir_missing(self):
1080 localdir = os.path.abspath("web/PUT_NEWDIRURL_localdir_missing")
1081 # we do *not* create it, to trigger an error
1082 url = (self.public_url + "/foo/subdir/newdir?t=upload&localdir=%s"
1083 % urllib.quote(localdir))
1084 d = self.shouldHTTPError2("test_PUT_NEWDIRURL_localdir_missing",
1086 "%s doesn't exist!" % localdir,
1090 def test_POST_upload(self):
1091 d = self.POST(self.public_url + "/foo", t="upload",
1092 file=("new.txt", self.NEWFILE_CONTENTS))
1094 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1095 d.addCallback(lambda res:
1096 self.failUnlessChildContentsAre(fn, u"new.txt",
1097 self.NEWFILE_CONTENTS))
1100 def test_POST_upload_unicode(self):
1101 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1102 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1103 d = self.POST(self.public_url + "/foo", t="upload",
1104 file=(filename, self.NEWFILE_CONTENTS))
1106 d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1107 d.addCallback(lambda res:
1108 self.failUnlessChildContentsAre(fn, filename,
1109 self.NEWFILE_CONTENTS))
1110 d.addCallback(lambda res: self.GET(target_url))
1111 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1112 self.NEWFILE_CONTENTS,
1116 def test_POST_upload_no_link(self):
1117 d = self.POST("/uri", t="upload",
1118 file=("new.txt", self.NEWFILE_CONTENTS))
1119 def _check_upload_results(page):
1120 # this should be a page which describes the results of the upload
1121 # that just finished.
1122 self.failUnless("Upload Results:" in page)
1123 self.failUnless("URI:" in page)
1124 uri_re = re.compile("URI: <tt><span>(.*)</span>")
1125 mo = uri_re.search(page)
1126 self.failUnless(mo, page)
1127 new_uri = mo.group(1)
1129 d.addCallback(_check_upload_results)
1130 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1133 def test_POST_upload_no_link_whendone(self):
1134 d = self.POST("/uri", t="upload", when_done="/",
1135 file=("new.txt", self.NEWFILE_CONTENTS))
1136 d.addBoth(self.shouldRedirect, "/")
1139 def test_POST_upload_no_link_mutable(self):
1140 d = self.POST("/uri", t="upload", mutable="true",
1141 file=("new.txt", self.NEWFILE_CONTENTS))
1142 def _check(new_uri):
1143 new_uri = new_uri.strip()
1145 self.failUnless(IMutableFileURI.providedBy(u))
1146 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
1147 n = self.s.create_node_from_uri(new_uri)
1148 return n.download_to_data()
1149 d.addCallback(_check)
1151 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1152 d.addCallback(_check2)
1155 def test_POST_upload_mutable(self):
1156 # this creates a mutable file
1157 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
1158 file=("new.txt", self.NEWFILE_CONTENTS))
1160 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1161 d.addCallback(lambda res:
1162 self.failUnlessChildContentsAre(fn, u"new.txt",
1163 self.NEWFILE_CONTENTS))
1164 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1166 self.failUnless(IMutableFileNode.providedBy(newnode))
1167 self.failUnless(newnode.is_mutable())
1168 self.failIf(newnode.is_readonly())
1169 self._mutable_uri = newnode.get_uri()
1172 # now upload it again and make sure that the URI doesn't change
1173 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
1174 d.addCallback(lambda res:
1175 self.POST(self.public_url + "/foo", t="upload",
1177 file=("new.txt", NEWER_CONTENTS)))
1178 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1179 d.addCallback(lambda res:
1180 self.failUnlessChildContentsAre(fn, u"new.txt",
1182 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1184 self.failUnless(IMutableFileNode.providedBy(newnode))
1185 self.failUnless(newnode.is_mutable())
1186 self.failIf(newnode.is_readonly())
1187 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1188 d.addCallback(_got2)
1190 # finally list the directory, since mutable files are displayed
1193 d.addCallback(lambda res:
1194 self.GET(self.public_url + "/foo",
1195 followRedirect=True))
1196 def _check_page(res):
1197 # TODO: assert more about the contents
1198 self.failUnless("Overwrite" in res)
1199 self.failUnless("Choose new file:" in res)
1201 d.addCallback(_check_page)
1203 # test that clicking on the "overwrite" button works
1204 EVEN_NEWER_CONTENTS = NEWER_CONTENTS + "even newer\n"
1205 def _parse_overwrite_form_and_submit(res):
1206 OVERWRITE_FORM_RE=re.compile('<form action="([^"]*)" method="post" .*<input type="hidden" name="t" value="overwrite" /><input type="hidden" name="name" value="([^"]*)" /><input type="hidden" name="when_done" value="([^"]*)" />', re.I)
1207 mo = OVERWRITE_FORM_RE.search(res)
1209 formaction=mo.group(1)
1210 formname=mo.group(2)
1211 formwhendone=mo.group(3)
1213 if formaction == ".":
1214 formaction = self.public_url + "/foo"
1215 return self.POST(formaction, t="overwrite", name=formname, when_done=formwhendone, file=("new.txt", EVEN_NEWER_CONTENTS), followRedirect=False)
1216 d.addCallback(_parse_overwrite_form_and_submit)
1217 d.addBoth(self.shouldRedirect, urllib.quote(self.public_url + "/foo/"))
1218 d.addCallback(lambda res:
1219 self.failUnlessChildContentsAre(fn, u"new.txt",
1220 EVEN_NEWER_CONTENTS))
1221 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1223 self.failUnless(IMutableFileNode.providedBy(newnode))
1224 self.failUnless(newnode.is_mutable())
1225 self.failIf(newnode.is_readonly())
1226 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1227 d.addCallback(_got3)
1231 def test_POST_upload_replace(self):
1232 d = self.POST(self.public_url + "/foo", t="upload",
1233 file=("bar.txt", self.NEWFILE_CONTENTS))
1235 d.addCallback(self.failUnlessURIMatchesChild, fn, u"bar.txt")
1236 d.addCallback(lambda res:
1237 self.failUnlessChildContentsAre(fn, u"bar.txt",
1238 self.NEWFILE_CONTENTS))
1241 def test_POST_upload_no_replace_ok(self):
1242 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1243 file=("new.txt", self.NEWFILE_CONTENTS))
1244 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
1245 d.addCallback(lambda res: self.failUnlessEqual(res,
1246 self.NEWFILE_CONTENTS))
1249 def test_POST_upload_no_replace_queryarg(self):
1250 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1251 file=("bar.txt", self.NEWFILE_CONTENTS))
1252 d.addBoth(self.shouldFail, error.Error,
1253 "POST_upload_no_replace_queryarg",
1255 "There was already a child by that name, and you asked me "
1256 "to not replace it")
1257 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1258 d.addCallback(self.failUnlessIsBarDotTxt)
1261 def test_POST_upload_no_replace_field(self):
1262 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
1263 file=("bar.txt", self.NEWFILE_CONTENTS))
1264 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
1266 "There was already a child by that name, and you asked me "
1267 "to not replace it")
1268 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1269 d.addCallback(self.failUnlessIsBarDotTxt)
1272 def test_POST_upload_whendone(self):
1273 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
1274 file=("new.txt", self.NEWFILE_CONTENTS))
1275 d.addBoth(self.shouldRedirect, "/THERE")
1277 d.addCallback(lambda res:
1278 self.failUnlessChildContentsAre(fn, u"new.txt",
1279 self.NEWFILE_CONTENTS))
1282 def test_POST_upload_named(self):
1284 d = self.POST(self.public_url + "/foo", t="upload",
1285 name="new.txt", file=self.NEWFILE_CONTENTS)
1286 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1287 d.addCallback(lambda res:
1288 self.failUnlessChildContentsAre(fn, u"new.txt",
1289 self.NEWFILE_CONTENTS))
1292 def test_POST_upload_named_badfilename(self):
1293 d = self.POST(self.public_url + "/foo", t="upload",
1294 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
1295 d.addBoth(self.shouldFail, error.Error,
1296 "test_POST_upload_named_badfilename",
1298 "name= may not contain a slash",
1300 # make sure that nothing was added
1301 d.addCallback(lambda res:
1302 self.failUnlessNodeKeysAre(self._foo_node,
1303 [u"bar.txt", u"blockingfile",
1304 u"empty", u"n\u00fc.txt",
1308 def test_POST_mkdir(self): # return value?
1309 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
1310 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1311 d.addCallback(self.failUnlessNodeKeysAre, [])
1314 def test_POST_mkdir_no_parentdir_noredirect(self):
1315 d = self.POST("/uri?t=mkdir")
1316 def _after_mkdir(res):
1317 uri.NewDirectoryURI.init_from_string(res)
1318 d.addCallback(_after_mkdir)
1321 def test_POST_mkdir_no_parentdir_redirect(self):
1322 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
1323 d.addBoth(self.shouldRedirect, None, statuscode='303')
1324 def _check_target(target):
1325 target = urllib.unquote(target)
1326 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
1327 d.addCallback(_check_target)
1330 def test_POST_noparent_bad(self):
1331 d = self.shouldHTTPError2("POST /uri?t=bogus", 400, "Bad Request",
1332 "/uri accepts only PUT, PUT?t=mkdir, "
1333 "POST?t=upload, and POST?t=mkdir",
1334 self.POST, "/uri?t=bogus")
1337 def test_welcome_page_mkdir_button(self):
1338 # Fetch the welcome page.
1340 def _after_get_welcome_page(res):
1341 MKDIR_BUTTON_RE=re.compile('<form action="([^"]*)" method="post".*<input type="hidden" name="t" value="([^"]*)" /><input type="hidden" name="([^"]*)" value="([^"]*)" /><input type="submit" value="Create Directory!" />', re.I)
1342 mo = MKDIR_BUTTON_RE.search(res)
1343 formaction = mo.group(1)
1345 formaname = mo.group(3)
1346 formavalue = mo.group(4)
1347 return (formaction, formt, formaname, formavalue)
1348 d.addCallback(_after_get_welcome_page)
1349 def _after_parse_form(res):
1350 (formaction, formt, formaname, formavalue) = res
1351 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
1352 d.addCallback(_after_parse_form)
1353 d.addBoth(self.shouldRedirect, None, statuscode='303')
1356 def test_POST_mkdir_replace(self): # return value?
1357 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
1358 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1359 d.addCallback(self.failUnlessNodeKeysAre, [])
1362 def test_POST_mkdir_no_replace_queryarg(self): # return value?
1363 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
1364 d.addBoth(self.shouldFail, error.Error,
1365 "POST_mkdir_no_replace_queryarg",
1367 "There was already a child by that name, and you asked me "
1368 "to not replace it")
1369 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1370 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1373 def test_POST_mkdir_no_replace_field(self): # return value?
1374 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
1376 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
1378 "There was already a child by that name, and you asked me "
1379 "to not replace it")
1380 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1381 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1384 def test_POST_mkdir_whendone_field(self):
1385 d = self.POST(self.public_url + "/foo",
1386 t="mkdir", name="newdir", when_done="/THERE")
1387 d.addBoth(self.shouldRedirect, "/THERE")
1388 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1389 d.addCallback(self.failUnlessNodeKeysAre, [])
1392 def test_POST_mkdir_whendone_queryarg(self):
1393 d = self.POST(self.public_url + "/foo?when_done=/THERE",
1394 t="mkdir", name="newdir")
1395 d.addBoth(self.shouldRedirect, "/THERE")
1396 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1397 d.addCallback(self.failUnlessNodeKeysAre, [])
1400 def test_POST_bad_t(self):
1401 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
1403 self.POST, self.public_url + "/foo", t="BOGUS")
1406 def test_POST_set_children(self):
1407 contents9, n9, newuri9 = self.makefile(9)
1408 contents10, n10, newuri10 = self.makefile(10)
1409 contents11, n11, newuri11 = self.makefile(11)
1412 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
1415 "ctime": 1002777696.7564139,
1416 "mtime": 1002777696.7564139
1419 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
1422 "ctime": 1002777696.7564139,
1423 "mtime": 1002777696.7564139
1426 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
1429 "ctime": 1002777696.7564139,
1430 "mtime": 1002777696.7564139
1433 }""" % (newuri9, newuri10, newuri11)
1435 url = self.webish_url + self.public_url + "/foo" + "?t=set_children"
1437 d = client.getPage(url, method="POST", postdata=reqbody)
1439 self.failUnlessURIMatchesChild(newuri9, self._foo_node, u"atomic_added_1")
1440 self.failUnlessURIMatchesChild(newuri10, self._foo_node, u"atomic_added_2")
1441 self.failUnlessURIMatchesChild(newuri11, self._foo_node, u"atomic_added_3")
1443 d.addCallback(_then)
1446 def test_POST_put_uri(self):
1447 contents, n, newuri = self.makefile(8)
1448 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
1449 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
1450 d.addCallback(lambda res:
1451 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1455 def test_POST_put_uri_replace(self):
1456 contents, n, newuri = self.makefile(8)
1457 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
1458 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
1459 d.addCallback(lambda res:
1460 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1464 def test_POST_put_uri_no_replace_queryarg(self):
1465 contents, n, newuri = self.makefile(8)
1466 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
1467 name="bar.txt", uri=newuri)
1468 d.addBoth(self.shouldFail, error.Error,
1469 "POST_put_uri_no_replace_queryarg",
1471 "There was already a child by that name, and you asked me "
1472 "to not replace it")
1473 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1474 d.addCallback(self.failUnlessIsBarDotTxt)
1477 def test_POST_put_uri_no_replace_field(self):
1478 contents, n, newuri = self.makefile(8)
1479 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
1480 name="bar.txt", uri=newuri)
1481 d.addBoth(self.shouldFail, error.Error,
1482 "POST_put_uri_no_replace_field",
1484 "There was already a child by that name, and you asked me "
1485 "to not replace it")
1486 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1487 d.addCallback(self.failUnlessIsBarDotTxt)
1490 def test_POST_delete(self):
1491 d = self.POST(self.public_url + "/foo", t="delete", name="bar.txt")
1492 d.addCallback(lambda res: self._foo_node.list())
1493 def _check(children):
1494 self.failIf(u"bar.txt" in children)
1495 d.addCallback(_check)
1498 def test_POST_rename_file(self):
1499 d = self.POST(self.public_url + "/foo", t="rename",
1500 from_name="bar.txt", to_name='wibble.txt')
1501 d.addCallback(lambda res:
1502 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1503 d.addCallback(lambda res:
1504 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
1505 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
1506 d.addCallback(self.failUnlessIsBarDotTxt)
1507 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
1508 d.addCallback(self.failUnlessIsBarJSON)
1511 def test_POST_rename_file_replace(self):
1512 # rename a file and replace a directory with it
1513 d = self.POST(self.public_url + "/foo", t="rename",
1514 from_name="bar.txt", to_name='empty')
1515 d.addCallback(lambda res:
1516 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1517 d.addCallback(lambda res:
1518 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
1519 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
1520 d.addCallback(self.failUnlessIsBarDotTxt)
1521 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
1522 d.addCallback(self.failUnlessIsBarJSON)
1525 def test_POST_rename_file_no_replace_queryarg(self):
1526 # rename a file and replace a directory with it
1527 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
1528 from_name="bar.txt", to_name='empty')
1529 d.addBoth(self.shouldFail, error.Error,
1530 "POST_rename_file_no_replace_queryarg",
1532 "There was already a child by that name, and you asked me "
1533 "to not replace it")
1534 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
1535 d.addCallback(self.failUnlessIsEmptyJSON)
1538 def test_POST_rename_file_no_replace_field(self):
1539 # rename a file and replace a directory with it
1540 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
1541 from_name="bar.txt", to_name='empty')
1542 d.addBoth(self.shouldFail, error.Error,
1543 "POST_rename_file_no_replace_field",
1545 "There was already a child by that name, and you asked me "
1546 "to not replace it")
1547 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
1548 d.addCallback(self.failUnlessIsEmptyJSON)
1551 def failUnlessIsEmptyJSON(self, res):
1552 data = simplejson.loads(res)
1553 self.failUnlessEqual(data[0], "dirnode", data)
1554 self.failUnlessEqual(len(data[1]["children"]), 0)
1556 def test_POST_rename_file_slash_fail(self):
1557 d = self.POST(self.public_url + "/foo", t="rename",
1558 from_name="bar.txt", to_name='kirk/spock.txt')
1559 d.addBoth(self.shouldFail, error.Error,
1560 "test_POST_rename_file_slash_fail",
1562 "to_name= may not contain a slash",
1564 d.addCallback(lambda res:
1565 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
1566 d.addCallback(lambda res: self.POST(self.public_url, t="rename",
1567 from_name="foo/bar.txt", to_name='george.txt'))
1568 d.addBoth(self.shouldFail, error.Error,
1569 "test_POST_rename_file_slash_fail",
1571 "from_name= may not contain a slash",
1573 d.addCallback(lambda res:
1574 self.failUnlessNodeHasChild(self.public_root, u"foo"))
1575 d.addCallback(lambda res:
1576 self.failIfNodeHasChild(self.public_root, u"george.txt"))
1577 d.addCallback(lambda res:
1578 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
1579 d.addCallback(lambda res: self.GET(self.public_url + "/foo?t=json"))
1580 d.addCallback(self.failUnlessIsFooJSON)
1583 def test_POST_rename_dir(self):
1584 d = self.POST(self.public_url, t="rename",
1585 from_name="foo", to_name='plunk')
1586 d.addCallback(lambda res:
1587 self.failIfNodeHasChild(self.public_root, u"foo"))
1588 d.addCallback(lambda res:
1589 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
1590 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
1591 d.addCallback(self.failUnlessIsFooJSON)
1594 def shouldRedirect(self, res, target=None, statuscode=None):
1595 """ If target is not None then the redirection has to go to target. If
1596 statuscode is not None then the redirection has to be accomplished with
1597 that HTTP status code."""
1598 if not isinstance(res, failure.Failure):
1599 self.fail("we were expecting to get redirected %s, not get an"
1600 " actual page: %s" % ((target is None) and "somewhere" or ("to " + target), res))
1601 res.trap(error.PageRedirect)
1602 if statuscode is not None:
1603 self.failUnlessEqual(res.value.status, statuscode)
1604 if target is not None:
1605 # the PageRedirect does not seem to capture the uri= query arg
1606 # properly, so we can't check for it.
1607 realtarget = self.webish_url + target
1608 self.failUnlessEqual(res.value.location, realtarget)
1609 return res.value.location
1611 def test_GET_URI_form(self):
1612 base = "/uri?uri=%s" % self._bar_txt_uri
1613 # this is supposed to give us a redirect to /uri/$URI, plus arguments
1614 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
1616 d.addBoth(self.shouldRedirect, targetbase)
1617 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
1618 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
1619 d.addCallback(lambda res: self.GET(base+"&t=json"))
1620 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
1621 d.addCallback(self.log, "about to get file by uri")
1622 d.addCallback(lambda res: self.GET(base, followRedirect=True))
1623 d.addCallback(self.failUnlessIsBarDotTxt)
1624 d.addCallback(self.log, "got file by uri, about to get dir by uri")
1625 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
1626 followRedirect=True))
1627 d.addCallback(self.failUnlessIsFooJSON)
1628 d.addCallback(self.log, "got dir by uri")
1632 def test_GET_rename_form(self):
1633 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
1634 followRedirect=True) # XXX [ ] todo: figure out why '.../foo' doesn't work
1636 self.failUnless(re.search(r'name="when_done" value=".*%s/foo/' % (urllib.quote(self.public_url),), res), (r'name="when_done" value=".*%s/foo/' % (urllib.quote(self.public_url),), res,))
1637 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
1638 d.addCallback(_check)
1641 def log(self, res, msg):
1642 #print "MSG: %s RES: %s" % (msg, res)
1646 def test_GET_URI_URL(self):
1647 base = "/uri/%s" % self._bar_txt_uri
1649 d.addCallback(self.failUnlessIsBarDotTxt)
1650 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
1651 d.addCallback(self.failUnlessIsBarDotTxt)
1652 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
1653 d.addCallback(self.failUnlessIsBarDotTxt)
1656 def test_GET_URI_URL_dir(self):
1657 base = "/uri/%s?t=json" % self._foo_uri
1659 d.addCallback(self.failUnlessIsFooJSON)
1662 def test_GET_URI_URL_missing(self):
1663 base = "/uri/%s" % self._bad_file_uri
1665 d.addBoth(self.shouldHTTPError, "test_GET_URI_URL_missing",
1666 http.GONE, response_substring="NotEnoughSharesError")
1667 # TODO: how can we exercise both sides of WebDownloadTarget.fail
1668 # here? we must arrange for a download to fail after target.open()
1669 # has been called, and then inspect the response to see that it is
1670 # shorter than we expected.
1673 def test_PUT_NEWFILEURL_uri(self):
1674 contents, n, new_uri = self.makefile(8)
1675 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
1676 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
1677 d.addCallback(lambda res:
1678 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1682 def test_PUT_NEWFILEURL_uri_replace(self):
1683 contents, n, new_uri = self.makefile(8)
1684 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
1685 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
1686 d.addCallback(lambda res:
1687 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1691 def test_PUT_NEWFILEURL_uri_no_replace(self):
1692 contents, n, new_uri = self.makefile(8)
1693 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
1694 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_uri_no_replace",
1696 "There was already a child by that name, and you asked me "
1697 "to not replace it")
1700 def test_PUT_NEWFILE_URI(self):
1701 file_contents = "New file contents here\n"
1702 d = self.PUT("/uri", file_contents)
1704 self.failUnless(uri in FakeCHKFileNode.all_contents)
1705 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
1707 return self.GET("/uri/%s" % uri)
1708 d.addCallback(_check)
1710 self.failUnlessEqual(res, file_contents)
1711 d.addCallback(_check2)
1714 def test_PUT_NEWFILE_URI_only_PUT(self):
1715 d = self.PUT("/uri?t=bogus", "")
1716 d.addBoth(self.shouldFail, error.Error,
1717 "PUT_NEWFILE_URI_only_PUT",
1719 "/uri only accepts PUT and PUT?t=mkdir")
1722 def test_PUT_NEWFILE_URI_mutable(self):
1723 file_contents = "New file contents here\n"
1724 d = self.PUT("/uri?mutable=true", file_contents)
1725 def _check_mutable(uri):
1728 self.failUnless(IMutableFileURI.providedBy(u))
1729 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
1730 n = self.s.create_node_from_uri(uri)
1731 return n.download_to_data()
1732 d.addCallback(_check_mutable)
1733 def _check2_mutable(data):
1734 self.failUnlessEqual(data, file_contents)
1735 d.addCallback(_check2_mutable)
1739 self.failUnless(uri in FakeCHKFileNode.all_contents)
1740 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
1742 return self.GET("/uri/%s" % uri)
1743 d.addCallback(_check)
1745 self.failUnlessEqual(res, file_contents)
1746 d.addCallback(_check2)
1749 def test_PUT_mkdir(self):
1750 d = self.PUT("/uri?t=mkdir", "")
1752 n = self.s.create_node_from_uri(uri.strip())
1753 d2 = self.failUnlessNodeKeysAre(n, [])
1754 d2.addCallback(lambda res:
1755 self.GET("/uri/%s?t=json" % uri))
1757 d.addCallback(_check)
1758 d.addCallback(self.failUnlessIsEmptyJSON)
1761 def test_POST_check(self):
1762 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
1764 # this returns a string form of the results, which are probably
1765 # None since we're using fake filenodes.
1766 # TODO: verify that the check actually happened, by changing
1767 # FakeCHKFileNode to count how many times .check() has been
1770 d.addCallback(_done)
1773 def test_bad_method(self):
1774 url = self.webish_url + self.public_url + "/foo/bar.txt"
1775 d = self.shouldHTTPError2("test_bad_method", 404, "Not Found", None,
1776 client.getPage, url, method="BOGUS")
1779 def test_short_url(self):
1780 url = self.webish_url + "/uri"
1781 d = self.shouldHTTPError2("test_short_url", 404, "Not Found", None,
1782 client.getPage, url, method="DELETE")