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()]
39 def connected_to_introducer(self):
42 def create_node_from_uri(self, auri):
43 u = uri.from_string(auri)
44 if (INewDirectoryURI.providedBy(u)
45 or IReadonlyNewDirectoryURI.providedBy(u)):
46 return FakeDirectoryNode(self).init_from_uri(u)
47 if IFileURI.providedBy(u):
48 return FakeCHKFileNode(u, self)
49 assert IMutableFileURI.providedBy(u), u
50 return FakeMutableFileNode(self).init_from_uri(u)
52 def create_empty_dirnode(self):
53 n = FakeDirectoryNode(self)
55 d.addCallback(lambda res: n)
58 def create_mutable_file(self, contents=""):
59 n = FakeMutableFileNode(self)
60 return n.create(contents)
62 def upload(self, uploadable):
63 d = uploadable.get_size()
64 d.addCallback(lambda size: uploadable.read(size))
67 n = create_chk_filenode(self, data)
68 results = upload.UploadResults()
69 results.uri = n.get_uri()
71 d.addCallback(_got_data)
74 def list_all_uploads(self):
76 def list_all_downloads(self):
79 def list_active_uploads(self):
80 return self._all_upload_status
81 def list_active_downloads(self):
82 return self._all_download_status
83 def list_active_publish(self):
85 def list_active_retrieve(self):
88 def list_recent_uploads(self):
89 return self._all_upload_status
90 def list_recent_downloads(self):
91 return self._all_download_status
92 def list_recent_publish(self):
94 def list_recent_retrieve(self):
98 class WebMixin(object):
100 self.s = FakeClient()
101 self.s.startService()
102 self.ws = s = webish.WebishServer("0")
103 s.allow_local_access(True)
104 s.setServiceParent(self.s)
105 port = s.listener._port.getHost().port
106 self.webish_url = "http://localhost:%d" % port
108 l = [ self.s.create_empty_dirnode() for x in range(6) ]
109 d = defer.DeferredList(l)
111 self.public_root = res[0][1]
112 assert interfaces.IDirectoryNode.providedBy(self.public_root), res
113 self.public_url = "/uri/" + self.public_root.get_uri()
114 self.private_root = res[1][1]
118 self._foo_uri = foo.get_uri()
119 self._foo_readonly_uri = foo.get_readonly_uri()
120 # NOTE: we ignore the deferred on all set_uri() calls, because we
121 # know the fake nodes do these synchronously
122 self.public_root.set_uri(u"foo", foo.get_uri())
124 self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
125 foo.set_uri(u"bar.txt", self._bar_txt_uri)
127 foo.set_uri(u"empty", res[3][1].get_uri())
128 sub_uri = res[4][1].get_uri()
129 foo.set_uri(u"sub", sub_uri)
130 sub = self.s.create_node_from_uri(sub_uri)
132 _ign, n, blocking_uri = self.makefile(1)
133 foo.set_uri(u"blockingfile", blocking_uri)
135 unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
136 # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
137 # still think of it as an umlaut
138 foo.set_uri(unicode_filename, self._bar_txt_uri)
140 _ign, n, baz_file = self.makefile(2)
141 sub.set_uri(u"baz.txt", baz_file)
143 _ign, n, self._bad_file_uri = self.makefile(3)
144 # this uri should not be downloadable
145 del FakeCHKFileNode.all_contents[self._bad_file_uri]
148 self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri())
149 rodir.set_uri(u"nor", baz_file)
154 # public/foo/blockingfile
157 # public/foo/sub/baz.txt
159 # public/reedownlee/nor
160 self.NEWFILE_CONTENTS = "newfile contents\n"
162 return foo.get_metadata_for(u"bar.txt")
164 def _got_metadata(metadata):
165 self._bar_txt_metadata = metadata
166 d.addCallback(_got_metadata)
169 def makefile(self, number):
170 contents = "contents of file %s\n" % number
171 n = create_chk_filenode(self.s, contents)
172 return contents, n, n.get_uri()
175 return self.s.stopService()
177 def failUnlessIsBarDotTxt(self, res):
178 self.failUnlessEqual(res, self.BAR_CONTENTS, res)
180 def failUnlessIsBarJSON(self, res):
181 data = simplejson.loads(res)
182 self.failUnless(isinstance(data, list))
183 self.failUnlessEqual(data[0], "filenode")
184 self.failUnless(isinstance(data[1], dict))
185 self.failIf("rw_uri" in data[1]) # immutable
186 self.failUnlessEqual(data[1]["ro_uri"], self._bar_txt_uri)
187 self.failUnlessEqual(data[1]["size"], len(self.BAR_CONTENTS))
189 def failUnlessIsFooJSON(self, res):
190 data = simplejson.loads(res)
191 self.failUnless(isinstance(data, list))
192 self.failUnlessEqual(data[0], "dirnode", res)
193 self.failUnless(isinstance(data[1], dict))
194 self.failUnless("rw_uri" in data[1]) # mutable
195 self.failUnlessEqual(data[1]["rw_uri"], self._foo_uri)
196 self.failUnlessEqual(data[1]["ro_uri"], self._foo_readonly_uri)
198 kidnames = sorted(data[1]["children"])
199 self.failUnlessEqual(kidnames,
200 [u"bar.txt", u"blockingfile", u"empty",
201 u"n\u00fc.txt", u"sub"])
202 kids = data[1]["children"]
203 self.failUnlessEqual(kids[u"sub"][0], "dirnode")
204 self.failUnless("metadata" in kids[u"sub"][1])
205 self.failUnless("ctime" in kids[u"sub"][1]["metadata"])
206 self.failUnless("mtime" in kids[u"sub"][1]["metadata"])
207 self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
208 self.failUnlessEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
209 self.failUnlessEqual(kids[u"bar.txt"][1]["ro_uri"], self._bar_txt_uri)
210 self.failUnlessEqual(kids[u"bar.txt"][1]["metadata"]["ctime"],
211 self._bar_txt_metadata["ctime"])
212 self.failUnlessEqual(kids[u"n\u00fc.txt"][1]["ro_uri"],
215 def GET(self, urlpath, followRedirect=False):
216 url = self.webish_url + urlpath
217 return client.getPage(url, method="GET", followRedirect=followRedirect)
219 def PUT(self, urlpath, data):
220 url = self.webish_url + urlpath
221 return client.getPage(url, method="PUT", postdata=data)
223 def DELETE(self, urlpath):
224 url = self.webish_url + urlpath
225 return client.getPage(url, method="DELETE")
227 def POST(self, urlpath, followRedirect=False, **fields):
228 url = self.webish_url + urlpath
229 sepbase = "boogabooga"
233 form.append('Content-Disposition: form-data; name="_charset"')
237 for name, value in fields.iteritems():
238 if isinstance(value, tuple):
239 filename, value = value
240 form.append('Content-Disposition: form-data; name="%s"; '
241 'filename="%s"' % (name, filename.encode("utf-8")))
243 form.append('Content-Disposition: form-data; name="%s"' % name)
245 form.append(str(value))
248 body = "\r\n".join(form) + "\r\n"
249 headers = {"content-type": "multipart/form-data; boundary=%s" % sepbase,
251 return client.getPage(url, method="POST", postdata=body,
252 headers=headers, followRedirect=followRedirect)
254 def shouldFail(self, res, expected_failure, which,
255 substring=None, response_substring=None):
256 if isinstance(res, failure.Failure):
257 res.trap(expected_failure)
259 self.failUnless(substring in str(res),
260 "substring '%s' not in '%s'"
261 % (substring, str(res)))
262 if response_substring:
263 self.failUnless(response_substring in res.value.response,
264 "respose substring '%s' not in '%s'"
265 % (response_substring, res.value.response))
267 self.fail("%s was supposed to raise %s, not get '%s'" %
268 (which, expected_failure, res))
270 def shouldFail2(self, expected_failure, which, substring,
271 callable, *args, **kwargs):
272 assert substring is None or isinstance(substring, str)
273 d = defer.maybeDeferred(callable, *args, **kwargs)
275 if isinstance(res, failure.Failure):
276 res.trap(expected_failure)
278 self.failUnless(substring in str(res),
279 "substring '%s' not in '%s'"
280 % (substring, str(res)))
282 self.fail("%s was supposed to raise %s, not get '%s'" %
283 (which, expected_failure, res))
287 def should404(self, res, which):
288 if isinstance(res, failure.Failure):
289 res.trap(error.Error)
290 self.failUnlessEqual(res.value.status, "404")
292 self.fail("%s was supposed to Error(404), not get '%s'" %
295 def shouldHTTPError(self, res, which, code=None, substring=None,
296 response_substring=None):
297 if isinstance(res, failure.Failure):
298 res.trap(error.Error)
300 self.failUnlessEqual(res.value.status, str(code))
302 self.failUnless(substring in str(res),
303 "substring '%s' not in '%s'"
304 % (substring, str(res)))
305 if response_substring:
306 self.failUnless(response_substring in res.value.response,
307 "respose substring '%s' not in '%s'"
308 % (response_substring, res.value.response))
310 self.fail("%s was supposed to Error(%s), not get '%s'" %
313 def shouldHTTPError2(self, which,
314 code=None, substring=None, response_substring=None,
315 callable=None, *args, **kwargs):
316 assert substring is None or isinstance(substring, str)
318 d = defer.maybeDeferred(callable, *args, **kwargs)
319 d.addBoth(self.shouldHTTPError, which,
320 code, substring, response_substring)
324 class Web(WebMixin, unittest.TestCase):
325 def test_create(self):
328 def test_welcome(self):
331 self.failUnless('Welcome To AllMyData' in res)
332 self.failUnless('Tahoe' in res)
334 self.s.basedir = 'web/test_welcome'
335 fileutil.make_dirs("web/test_welcome")
336 fileutil.make_dirs("web/test_welcome/private")
338 d.addCallback(_check)
341 def test_provisioning_math(self):
342 self.failUnlessEqual(provisioning.binomial(10, 0), 1)
343 self.failUnlessEqual(provisioning.binomial(10, 1), 10)
344 self.failUnlessEqual(provisioning.binomial(10, 2), 45)
345 self.failUnlessEqual(provisioning.binomial(10, 9), 10)
346 self.failUnlessEqual(provisioning.binomial(10, 10), 1)
348 def test_provisioning(self):
349 d = self.GET("/provisioning/")
351 self.failUnless('Tahoe Provisioning Tool' in res)
352 fields = {'filled': True,
353 "num_users": int(50e3),
354 "files_per_user": 1000,
355 "space_per_user": int(1e9),
356 "sharing_ratio": 1.0,
357 "encoding_parameters": "3-of-10-5",
359 "ownership_mode": "A",
360 "download_rate": 100,
365 return self.POST("/provisioning/", **fields)
367 d.addCallback(_check)
369 self.failUnless('Tahoe Provisioning Tool' in res)
370 self.failUnless("Share space consumed: 167.01TB" in res)
372 fields = {'filled': True,
373 "num_users": int(50e6),
374 "files_per_user": 1000,
375 "space_per_user": int(5e9),
376 "sharing_ratio": 1.0,
377 "encoding_parameters": "25-of-100-50",
378 "num_servers": 30000,
379 "ownership_mode": "E",
380 "drive_failure_model": "U",
382 "download_rate": 1000,
387 return self.POST("/provisioning/", **fields)
388 d.addCallback(_check2)
390 self.failUnless("Share space consumed: huge!" in res)
391 fields = {'filled': True}
392 return self.POST("/provisioning/", **fields)
393 d.addCallback(_check3)
395 self.failUnless("Share space consumed:" in res)
396 d.addCallback(_check4)
399 def test_status(self):
400 dl_num = self.s.list_recent_downloads()[0].get_counter()
401 ul_num = self.s.list_recent_uploads()[0].get_counter()
402 d = self.GET("/status", followRedirect=True)
404 self.failUnless('Upload and Download Status' in res, res)
405 self.failUnless('"down-%d"' % dl_num in res, res)
406 self.failUnless('"up-%d"' % ul_num in res, res)
407 d.addCallback(_check)
408 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
410 self.failUnless("File Download Status" in res, res)
411 d.addCallback(_check_dl)
412 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
414 self.failUnless("File Upload Status" in res, res)
415 d.addCallback(_check_ul)
418 def test_status_numbers(self):
419 drrm = status.DownloadResultsRendererMixin()
420 self.failUnlessEqual(drrm.render_time(None, None), "")
421 self.failUnlessEqual(drrm.render_time(None, 2.5), "2.50s")
422 self.failUnlessEqual(drrm.render_time(None, 0.25), "250ms")
423 self.failUnlessEqual(drrm.render_time(None, 0.0021), "2.1ms")
424 self.failUnlessEqual(drrm.render_time(None, 0.000123), "123us")
425 self.failUnlessEqual(drrm.render_rate(None, None), "")
426 self.failUnlessEqual(drrm.render_rate(None, 2500000), "2.50MBps")
427 self.failUnlessEqual(drrm.render_rate(None, 30100), "30.1kBps")
428 self.failUnlessEqual(drrm.render_rate(None, 123), "123Bps")
430 urrm = status.UploadResultsRendererMixin()
431 self.failUnlessEqual(urrm.render_time(None, None), "")
432 self.failUnlessEqual(urrm.render_time(None, 2.5), "2.50s")
433 self.failUnlessEqual(urrm.render_time(None, 0.25), "250ms")
434 self.failUnlessEqual(urrm.render_time(None, 0.0021), "2.1ms")
435 self.failUnlessEqual(urrm.render_time(None, 0.000123), "123us")
436 self.failUnlessEqual(urrm.render_rate(None, None), "")
437 self.failUnlessEqual(urrm.render_rate(None, 2500000), "2.50MBps")
438 self.failUnlessEqual(urrm.render_rate(None, 30100), "30.1kBps")
439 self.failUnlessEqual(urrm.render_rate(None, 123), "123Bps")
441 def test_GET_FILEURL(self):
442 d = self.GET(self.public_url + "/foo/bar.txt")
443 d.addCallback(self.failUnlessIsBarDotTxt)
446 def test_GET_FILEURL_save(self):
447 d = self.GET(self.public_url + "/foo/bar.txt?save=bar.txt")
448 # TODO: look at the headers, expect a Content-Disposition: attachment
450 d.addCallback(self.failUnlessIsBarDotTxt)
453 def test_GET_FILEURL_download(self):
454 d = self.GET(self.public_url + "/foo/bar.txt?t=download")
455 d.addCallback(self.failUnlessIsBarDotTxt)
458 def test_GET_FILEURL_missing(self):
459 d = self.GET(self.public_url + "/foo/missing")
460 d.addBoth(self.should404, "test_GET_FILEURL_missing")
463 def test_PUT_NEWFILEURL(self):
464 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
465 # TODO: we lose the response code, so we can't check this
466 #self.failUnlessEqual(responsecode, 201)
467 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
468 d.addCallback(lambda res:
469 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
470 self.NEWFILE_CONTENTS))
473 def test_PUT_NEWFILEURL_replace(self):
474 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
475 # TODO: we lose the response code, so we can't check this
476 #self.failUnlessEqual(responsecode, 200)
477 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
478 d.addCallback(lambda res:
479 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
480 self.NEWFILE_CONTENTS))
483 def test_PUT_NEWFILEURL_no_replace(self):
484 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
485 self.NEWFILE_CONTENTS)
486 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
488 "There was already a child by that name, and you asked me "
492 def test_PUT_NEWFILEURL_mkdirs(self):
493 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
495 d.addCallback(self.failUnlessURIMatchesChild, fn, u"newdir/new.txt")
496 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
497 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
498 d.addCallback(lambda res:
499 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
500 self.NEWFILE_CONTENTS))
503 def test_PUT_NEWFILEURL_blocked(self):
504 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
505 self.NEWFILE_CONTENTS)
506 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
508 "cannot create directory because there is a file in the way")
511 def test_DELETE_FILEURL(self):
512 d = self.DELETE(self.public_url + "/foo/bar.txt")
513 d.addCallback(lambda res:
514 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
517 def test_DELETE_FILEURL_missing(self):
518 d = self.DELETE(self.public_url + "/foo/missing")
519 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
522 def test_DELETE_FILEURL_missing2(self):
523 d = self.DELETE(self.public_url + "/missing/missing")
524 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
527 def test_GET_FILEURL_json(self):
528 # twisted.web.http.parse_qs ignores any query args without an '=', so
529 # I can't do "GET /path?json", I have to do "GET /path/t=json"
530 # instead. This may make it tricky to emulate the S3 interface
532 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
533 d.addCallback(self.failUnlessIsBarJSON)
536 def test_GET_FILEURL_json_missing(self):
537 d = self.GET(self.public_url + "/foo/missing?json")
538 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
541 def disable_local_access(self, res=None):
542 self.ws.allow_local_access(False)
545 def test_GET_FILEURL_localfile(self):
546 localfile = os.path.abspath("web/GET_FILEURL_local file")
547 url = (self.public_url + "/foo/bar.txt?t=download&localfile=%s" %
548 urllib.quote(localfile))
549 fileutil.make_dirs("web")
552 self.failUnless(os.path.exists(localfile))
553 data = open(localfile, "rb").read()
554 self.failUnlessEqual(data, self.BAR_CONTENTS)
558 def test_GET_FILEURL_localfile_disabled(self):
559 localfile = os.path.abspath("web/GET_FILEURL_local file_disabled")
560 url = (self.public_url + "/foo/bar.txt?t=download&localfile=%s" %
561 urllib.quote(localfile))
562 fileutil.make_dirs("web")
563 self.disable_local_access()
565 d.addBoth(self.shouldFail, error.Error, "localfile disabled",
567 "local file access is disabled")
570 def test_GET_FILEURL_localfile_nonlocal(self):
571 # TODO: somehow pretend that we aren't local, and verify that the
572 # server refuses to write to local files, probably by changing the
573 # server's idea of what counts as "local".
574 old_LOCALHOST = webish.LOCALHOST
575 webish.LOCALHOST = "127.0.0.2"
576 localfile = os.path.abspath("web/GET_FILEURL_local file_nonlocal")
577 fileutil.make_dirs("web")
578 d = self.GET(self.public_url + "/foo/bar.txt?t=download&localfile=%s"
579 % urllib.quote(localfile))
580 d.addBoth(self.shouldFail, error.Error, "localfile non-local",
582 "localfile= or localdir= requires a local connection")
584 self.failIf(os.path.exists(localfile))
585 d.addCallback(_check)
587 webish.LOCALHOST = old_LOCALHOST
592 def test_GET_FILEURL_localfile_nonabsolute(self):
593 localfile = "web/nonabsolute/path"
594 fileutil.make_dirs("web/nonabsolute")
595 d = self.GET(self.public_url + "/foo/bar.txt?t=download&localfile=%s"
596 % urllib.quote(localfile))
597 d.addBoth(self.shouldFail, error.Error, "localfile non-absolute",
599 "localfile= or localdir= requires an absolute path")
601 self.failIf(os.path.exists(localfile))
602 d.addCallback(_check)
605 def test_PUT_NEWFILEURL_localfile(self):
606 localfile = os.path.abspath("web/PUT_NEWFILEURL_local file")
607 url = (self.public_url + "/foo/new.txt?t=upload&localfile=%s" %
608 urllib.quote(localfile))
609 fileutil.make_dirs("web")
610 f = open(localfile, "wb")
611 f.write(self.NEWFILE_CONTENTS)
613 d = self.PUT(url, "")
614 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
615 d.addCallback(lambda res:
616 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
617 self.NEWFILE_CONTENTS))
620 def test_PUT_NEWFILEURL_localfile_missingarg(self):
621 url = self.public_url + "/foo/new.txt?t=upload"
622 d = self.shouldHTTPError2("test_PUT_NEWFILEURL_localfile_missing",
624 "t=upload requires localfile= or localdir=",
628 def test_PUT_NEWFILEURL_localfile_disabled(self):
629 localfile = os.path.abspath("web/PUT_NEWFILEURL_local file_disabled")
630 url = (self.public_url + "/foo/new.txt?t=upload&localfile=%s" %
631 urllib.quote(localfile))
632 fileutil.make_dirs("web")
633 f = open(localfile, "wb")
634 f.write(self.NEWFILE_CONTENTS)
636 self.disable_local_access()
637 d = self.PUT(url, "")
638 d.addBoth(self.shouldFail, error.Error, "put localfile disabled",
640 "local file access is disabled")
643 def test_PUT_NEWFILEURL_localfile_mkdirs(self):
644 localfile = os.path.abspath("web/PUT_NEWFILEURL_local file_mkdirs")
645 fileutil.make_dirs("web")
646 f = open(localfile, "wb")
647 f.write(self.NEWFILE_CONTENTS)
649 d = self.PUT(self.public_url + "/foo/newdir/new.txt?t=upload&localfile=%s"
650 % urllib.quote(localfile), "")
651 d.addCallback(self.failUnlessURIMatchesChild,
652 self._foo_node, u"newdir/new.txt")
653 d.addCallback(lambda res:
654 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
655 d.addCallback(lambda res:
656 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
657 d.addCallback(lambda res:
658 self.failUnlessChildContentsAre(self._foo_node,
660 self.NEWFILE_CONTENTS))
663 def test_GET_FILEURL_uri(self):
664 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
666 self.failUnlessEqual(res, self._bar_txt_uri)
667 d.addCallback(_check)
668 d.addCallback(lambda res:
669 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
671 # for now, for files, uris and readonly-uris are the same
672 self.failUnlessEqual(res, self._bar_txt_uri)
673 d.addCallback(_check2)
676 def test_GET_FILEURL_badtype(self):
677 d = self.shouldHTTPError2("GET t=bogus", 400, "Bad Request",
680 self.public_url + "/foo/bar.txt?t=bogus")
683 def test_GET_FILEURL_uri_missing(self):
684 d = self.GET(self.public_url + "/foo/missing?t=uri")
685 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
688 def test_GET_DIRURL(self):
689 # the addSlash means we get a redirect here
690 d = self.GET(self.public_url + "/foo", followRedirect=True)
692 # the FILE reference points to a URI, but it should end in bar.txt
693 self.failUnless(re.search(r'<td>'
694 '<a href="[^"]+bar.txt">bar.txt</a>'
697 '\s+<td>%d</td>' % len(self.BAR_CONTENTS)
699 # the DIR reference just points to a URI
700 self.failUnless(re.search(r'<td><a href="/uri/URI%3ADIR2%3A[^"]+">sub</a></td>'
701 '\s+<td>DIR</td>', res))
702 d.addCallback(_check)
704 # look at a directory which is readonly
705 d.addCallback(lambda res:
706 self.GET(self.public_url + "/reedownlee", followRedirect=True))
708 self.failUnless("(readonly)" in res, res)
709 self.failIf("Upload a file" in res, res)
710 d.addCallback(_check2)
712 # and at a directory that contains a readonly directory
713 d.addCallback(lambda res:
714 self.GET(self.public_url, followRedirect=True))
716 self.failUnless(re.search(r'<td><a href="/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a>'
717 '</td>\s+<td>DIR-RO</td>', res))
718 d.addCallback(_check3)
722 def test_GET_DIRURL_badtype(self):
723 d = self.shouldHTTPError2("test_GET_DIRURL_badtype",
727 self.public_url + "/foo?t=bogus")
730 def test_GET_DIRURL_json(self):
731 d = self.GET(self.public_url + "/foo?t=json")
732 d.addCallback(self.failUnlessIsFooJSON)
735 def test_GET_DIRURL_manifest(self):
736 d = self.GET(self.public_url + "/foo?t=manifest", followRedirect=True)
738 self.failUnless("Refresh Capabilities" in manifest)
742 def test_GET_DIRURL_uri(self):
743 d = self.GET(self.public_url + "/foo?t=uri")
745 self.failUnlessEqual(res, self._foo_uri)
746 d.addCallback(_check)
749 def test_GET_DIRURL_readonly_uri(self):
750 d = self.GET(self.public_url + "/foo?t=readonly-uri")
752 self.failUnlessEqual(res, self._foo_readonly_uri)
753 d.addCallback(_check)
756 def test_PUT_NEWDIRURL(self):
757 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
758 d.addCallback(lambda res:
759 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
760 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
761 d.addCallback(self.failUnlessNodeKeysAre, [])
764 def test_PUT_NEWDIRURL_replace(self):
765 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
766 d.addCallback(lambda res:
767 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
768 d.addCallback(lambda res: self._foo_node.get(u"sub"))
769 d.addCallback(self.failUnlessNodeKeysAre, [])
772 def test_PUT_NEWDIRURL_no_replace(self):
773 d = self.PUT(self.public_url + "/foo/sub?t=mkdir&replace=false", "")
774 d.addBoth(self.shouldFail, error.Error, "PUT_NEWDIRURL_no_replace",
776 "There was already a child by that name, and you asked me "
778 d.addCallback(lambda res:
779 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
780 d.addCallback(lambda res: self._foo_node.get(u"sub"))
781 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
784 def test_PUT_NEWDIRURL_mkdirs(self):
785 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
786 d.addCallback(lambda res:
787 self.failIfNodeHasChild(self._foo_node, u"newdir"))
788 d.addCallback(lambda res:
789 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
790 d.addCallback(lambda res:
791 self._foo_node.get_child_at_path(u"subdir/newdir"))
792 d.addCallback(self.failUnlessNodeKeysAre, [])
795 def test_DELETE_DIRURL(self):
796 d = self.DELETE(self.public_url + "/foo")
797 d.addCallback(lambda res:
798 self.failIfNodeHasChild(self.public_root, u"foo"))
801 def test_DELETE_DIRURL_missing(self):
802 d = self.DELETE(self.public_url + "/foo/missing")
803 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
804 d.addCallback(lambda res:
805 self.failUnlessNodeHasChild(self.public_root, u"foo"))
808 def test_DELETE_DIRURL_missing2(self):
809 d = self.DELETE(self.public_url + "/missing")
810 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
813 def test_walker(self):
815 def _visitor(path, node, metadata):
816 out.append((path, node))
817 return defer.succeed(None)
818 w = webish.DirnodeWalkerMixin()
819 d = w.walk(self.public_root, _visitor)
821 names = [path for (path,node) in out]
822 self.failUnlessEqual(sorted(names),
825 (u'foo',u'blockingfile'),
827 (u'foo', u"n\u00fc.txt"),
829 (u'foo',u'sub',u'baz.txt'),
831 (u'reedownlee', u'nor'),
833 subindex = names.index( (u'foo', u'sub') )
834 bazindex = names.index( (u'foo', u'sub', u'baz.txt') )
835 self.failUnless(subindex < bazindex)
836 for path,node in out:
837 if path[-1] in (u'bar.txt', u"n\u00fc.txt", u'blockingfile',
839 self.failUnless(interfaces.IFileNode.providedBy(node))
841 self.failUnless(interfaces.IDirectoryNode.providedBy(node))
842 d.addCallback(_check)
845 def test_GET_DIRURL_localdir(self):
846 localdir = os.path.abspath("web/GET_DIRURL_local dir")
847 fileutil.make_dirs("web")
848 d = self.GET(self.public_url + "/foo?t=download&localdir=%s" %
849 urllib.quote(localdir))
851 barfile = os.path.join(localdir, "bar.txt")
852 self.failUnless(os.path.exists(barfile))
853 data = open(barfile, "rb").read()
854 self.failUnlessEqual(data, self.BAR_CONTENTS)
855 blockingfile = os.path.join(localdir, "blockingfile")
856 self.failUnless(os.path.exists(blockingfile))
857 subdir = os.path.join(localdir, "sub")
858 self.failUnless(os.path.isdir(subdir))
859 d.addCallback(_check)
862 def test_GET_DIRURL_localdir_disabled(self):
863 localdir = os.path.abspath("web/GET_DIRURL_local dir_disabled")
864 fileutil.make_dirs("web")
865 self.disable_local_access()
866 d = self.GET(self.public_url + "/foo?t=download&localdir=%s" %
867 urllib.quote(localdir))
868 d.addBoth(self.shouldFail, error.Error, "localfile disabled",
870 "local file access is disabled")
873 def test_GET_DIRURL_localdir_nonabsolute(self):
874 localdir = "web/nonabsolute/dir path"
875 fileutil.make_dirs("web/nonabsolute")
876 d = self.GET(self.public_url + "/foo?t=download&localdir=%s" %
877 urllib.quote(localdir))
878 d.addBoth(self.shouldFail, error.Error, "localdir non-absolute",
880 "localfile= or localdir= requires an absolute path")
882 self.failIf(os.path.exists(localdir))
883 d.addCallback(_check)
886 def test_GET_DIRURL_localdir_nolocaldir(self):
887 d = self.shouldHTTPError2("GET_DIRURL_localdir_nolocaldir",
889 "t=download requires localdir=",
891 self.public_url + "/foo?t=download")
894 def touch(self, localdir, filename):
895 path = os.path.join(localdir, filename)
897 f.write("contents of %s\n" % filename)
902 w = webish.DirnodeWalkerMixin()
903 def visitor(childpath, childnode, metadata):
905 d = w.walk(self.public_root, visitor)
908 def failUnlessNodeKeysAre(self, node, expected_keys):
909 for k in expected_keys:
910 assert isinstance(k, unicode)
912 def _check(children):
913 self.failUnlessEqual(sorted(children.keys()), sorted(expected_keys))
914 d.addCallback(_check)
916 def failUnlessNodeHasChild(self, node, name):
917 assert isinstance(name, unicode)
919 def _check(children):
920 self.failUnless(name in children)
921 d.addCallback(_check)
923 def failIfNodeHasChild(self, node, name):
924 assert isinstance(name, unicode)
926 def _check(children):
927 self.failIf(name in children)
928 d.addCallback(_check)
931 def failUnlessChildContentsAre(self, node, name, expected_contents):
932 assert isinstance(name, unicode)
933 d = node.get_child_at_path(name)
934 d.addCallback(lambda node: node.download_to_data())
935 def _check(contents):
936 self.failUnlessEqual(contents, expected_contents)
937 d.addCallback(_check)
940 def failUnlessChildURIIs(self, node, name, expected_uri):
941 assert isinstance(name, unicode)
942 d = node.get_child_at_path(name)
944 self.failUnlessEqual(child.get_uri(), expected_uri.strip())
945 d.addCallback(_check)
948 def failUnlessURIMatchesChild(self, got_uri, node, name):
949 assert isinstance(name, unicode)
950 d = node.get_child_at_path(name)
952 self.failUnlessEqual(got_uri.strip(), child.get_uri())
953 d.addCallback(_check)
956 def failUnlessCHKURIHasContents(self, got_uri, contents):
957 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
959 def test_PUT_NEWDIRURL_localdir(self):
960 localdir = os.path.abspath("web/PUT_NEWDIRURL_local dir")
961 # create some files there
962 fileutil.make_dirs(os.path.join(localdir, "one"))
963 fileutil.make_dirs(os.path.join(localdir, "one/sub"))
964 fileutil.make_dirs(os.path.join(localdir, "two"))
965 fileutil.make_dirs(os.path.join(localdir, "three"))
966 self.touch(localdir, "three/foo.txt")
967 self.touch(localdir, "three/bar.txt")
968 self.touch(localdir, "zap.zip")
970 d = self.PUT(self.public_url + "/newdir?t=upload&localdir=%s"
971 % urllib.quote(localdir), "")
972 pr = self.public_root
973 d.addCallback(lambda res: self.failUnlessNodeHasChild(pr, u"newdir"))
974 d.addCallback(lambda res: pr.get(u"newdir"))
975 d.addCallback(self.failUnlessNodeKeysAre,
976 [u"one", u"two", u"three", u"zap.zip"])
977 d.addCallback(lambda res: pr.get_child_at_path(u"newdir/one"))
978 d.addCallback(self.failUnlessNodeKeysAre, [u"sub"])
979 d.addCallback(lambda res: pr.get_child_at_path(u"newdir/three"))
980 d.addCallback(self.failUnlessNodeKeysAre, [u"foo.txt", u"bar.txt"])
981 d.addCallback(lambda res: pr.get_child_at_path(u"newdir/three/bar.txt"))
982 d.addCallback(lambda barnode: barnode.download_to_data())
983 d.addCallback(lambda contents:
984 self.failUnlessEqual(contents,
985 "contents of three/bar.txt\n"))
988 def test_PUT_NEWDIRURL_localdir_disabled(self):
989 localdir = os.path.abspath("web/PUT_NEWDIRURL_local dir_disabled")
990 # create some files there
991 fileutil.make_dirs(os.path.join(localdir, "one"))
992 fileutil.make_dirs(os.path.join(localdir, "one/sub"))
993 fileutil.make_dirs(os.path.join(localdir, "two"))
994 fileutil.make_dirs(os.path.join(localdir, "three"))
995 self.touch(localdir, "three/foo.txt")
996 self.touch(localdir, "three/bar.txt")
997 self.touch(localdir, "zap.zip")
999 self.disable_local_access()
1000 d = self.PUT(self.public_url + "/newdir?t=upload&localdir=%s"
1001 % urllib.quote(localdir), "")
1002 d.addBoth(self.shouldFail, error.Error, "localfile disabled",
1004 "local file access is disabled")
1007 def test_PUT_NEWDIRURL_localdir_mkdirs(self):
1008 localdir = os.path.abspath("web/PUT_NEWDIRURL_local dir_mkdirs")
1009 # create some files there
1010 fileutil.make_dirs(os.path.join(localdir, "one"))
1011 fileutil.make_dirs(os.path.join(localdir, "one/sub"))
1012 fileutil.make_dirs(os.path.join(localdir, "two"))
1013 fileutil.make_dirs(os.path.join(localdir, "three"))
1014 self.touch(localdir, "three/foo.txt")
1015 self.touch(localdir, "three/bar.txt")
1016 self.touch(localdir, "zap.zip")
1018 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=upload&localdir=%s"
1019 % urllib.quote(localdir),
1022 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"subdir"))
1023 d.addCallback(lambda res: fn.get_child_at_path(u"subdir/newdir"))
1024 d.addCallback(self.failUnlessNodeKeysAre,
1025 [u"one", u"two", u"three", u"zap.zip"])
1026 d.addCallback(lambda res: fn.get_child_at_path(u"subdir/newdir/one"))
1027 d.addCallback(self.failUnlessNodeKeysAre, [u"sub"])
1028 d.addCallback(lambda res: fn.get_child_at_path(u"subdir/newdir/three"))
1029 d.addCallback(self.failUnlessNodeKeysAre, [u"foo.txt", u"bar.txt"])
1030 d.addCallback(lambda res:
1031 fn.get_child_at_path(u"subdir/newdir/three/bar.txt"))
1032 d.addCallback(lambda barnode: barnode.download_to_data())
1033 d.addCallback(lambda contents:
1034 self.failUnlessEqual(contents,
1035 "contents of three/bar.txt\n"))
1038 def test_PUT_NEWDIRURL_localdir_missing(self):
1039 localdir = os.path.abspath("web/PUT_NEWDIRURL_localdir_missing")
1040 # we do *not* create it, to trigger an error
1041 url = (self.public_url + "/foo/subdir/newdir?t=upload&localdir=%s"
1042 % urllib.quote(localdir))
1043 d = self.shouldHTTPError2("test_PUT_NEWDIRURL_localdir_missing",
1045 "%s doesn't exist!" % localdir,
1049 def test_POST_upload(self):
1050 d = self.POST(self.public_url + "/foo", t="upload",
1051 file=("new.txt", self.NEWFILE_CONTENTS))
1053 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1054 d.addCallback(lambda res:
1055 self.failUnlessChildContentsAre(fn, u"new.txt",
1056 self.NEWFILE_CONTENTS))
1059 def test_POST_upload_unicode(self):
1060 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1061 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1062 d = self.POST(self.public_url + "/foo", t="upload",
1063 file=(filename, self.NEWFILE_CONTENTS))
1065 d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1066 d.addCallback(lambda res:
1067 self.failUnlessChildContentsAre(fn, filename,
1068 self.NEWFILE_CONTENTS))
1069 d.addCallback(lambda res: self.GET(target_url))
1070 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1071 self.NEWFILE_CONTENTS,
1075 def test_POST_upload_no_link(self):
1076 d = self.POST("/uri", t="upload",
1077 file=("new.txt", self.NEWFILE_CONTENTS))
1078 def _check_upload_results(page):
1079 # this should be a page which describes the results of the upload
1080 # that just finished.
1081 self.failUnless("Upload Results:" in page)
1082 self.failUnless("URI:" in page)
1083 uri_re = re.compile("URI: <tt><span>(.*)</span>")
1084 mo = uri_re.search(page)
1085 self.failUnless(mo, page)
1086 new_uri = mo.group(1)
1088 d.addCallback(_check_upload_results)
1089 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1092 def test_POST_upload_no_link_whendone(self):
1093 d = self.POST("/uri", t="upload", when_done="/",
1094 file=("new.txt", self.NEWFILE_CONTENTS))
1095 d.addBoth(self.shouldRedirect, "/")
1098 def test_POST_upload_no_link_mutable(self):
1099 d = self.POST("/uri", t="upload", mutable="true",
1100 file=("new.txt", self.NEWFILE_CONTENTS))
1101 def _check(new_uri):
1102 new_uri = new_uri.strip()
1104 self.failUnless(IMutableFileURI.providedBy(u))
1105 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
1106 n = self.s.create_node_from_uri(new_uri)
1107 return n.download_to_data()
1108 d.addCallback(_check)
1110 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1111 d.addCallback(_check2)
1114 def test_POST_upload_mutable(self):
1115 # this creates a mutable file
1116 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
1117 file=("new.txt", self.NEWFILE_CONTENTS))
1119 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1120 d.addCallback(lambda res:
1121 self.failUnlessChildContentsAre(fn, u"new.txt",
1122 self.NEWFILE_CONTENTS))
1123 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1125 self.failUnless(IMutableFileNode.providedBy(newnode))
1126 self.failUnless(newnode.is_mutable())
1127 self.failIf(newnode.is_readonly())
1128 self._mutable_uri = newnode.get_uri()
1131 # now upload it again and make sure that the URI doesn't change
1132 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
1133 d.addCallback(lambda res:
1134 self.POST(self.public_url + "/foo", t="upload",
1136 file=("new.txt", NEWER_CONTENTS)))
1137 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1138 d.addCallback(lambda res:
1139 self.failUnlessChildContentsAre(fn, u"new.txt",
1141 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1143 self.failUnless(IMutableFileNode.providedBy(newnode))
1144 self.failUnless(newnode.is_mutable())
1145 self.failIf(newnode.is_readonly())
1146 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1147 d.addCallback(_got2)
1149 # finally list the directory, since mutable files are displayed
1152 d.addCallback(lambda res:
1153 self.GET(self.public_url + "/foo",
1154 followRedirect=True))
1155 def _check_page(res):
1156 # TODO: assert more about the contents
1157 self.failUnless("Overwrite" in res)
1158 self.failUnless("Choose new file:" in res)
1160 d.addCallback(_check_page)
1162 # test that clicking on the "overwrite" button works
1163 EVEN_NEWER_CONTENTS = NEWER_CONTENTS + "even newer\n"
1164 def _parse_overwrite_form_and_submit(res):
1165 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)
1166 mo = OVERWRITE_FORM_RE.search(res)
1168 formaction=mo.group(1)
1169 formname=mo.group(2)
1170 formwhendone=mo.group(3)
1172 if formaction == ".":
1173 formaction = self.public_url + "/foo"
1174 return self.POST(formaction, t="overwrite", name=formname, when_done=formwhendone, file=("new.txt", EVEN_NEWER_CONTENTS), followRedirect=False)
1175 d.addCallback(_parse_overwrite_form_and_submit)
1176 d.addBoth(self.shouldRedirect, urllib.quote(self.public_url + "/foo/"))
1177 d.addCallback(lambda res:
1178 self.failUnlessChildContentsAre(fn, u"new.txt",
1179 EVEN_NEWER_CONTENTS))
1180 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1182 self.failUnless(IMutableFileNode.providedBy(newnode))
1183 self.failUnless(newnode.is_mutable())
1184 self.failIf(newnode.is_readonly())
1185 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1186 d.addCallback(_got3)
1190 def test_POST_upload_replace(self):
1191 d = self.POST(self.public_url + "/foo", t="upload",
1192 file=("bar.txt", self.NEWFILE_CONTENTS))
1194 d.addCallback(self.failUnlessURIMatchesChild, fn, u"bar.txt")
1195 d.addCallback(lambda res:
1196 self.failUnlessChildContentsAre(fn, u"bar.txt",
1197 self.NEWFILE_CONTENTS))
1200 def test_POST_upload_no_replace_ok(self):
1201 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1202 file=("new.txt", self.NEWFILE_CONTENTS))
1203 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
1204 d.addCallback(lambda res: self.failUnlessEqual(res,
1205 self.NEWFILE_CONTENTS))
1208 def test_POST_upload_no_replace_queryarg(self):
1209 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1210 file=("bar.txt", self.NEWFILE_CONTENTS))
1211 d.addBoth(self.shouldFail, error.Error,
1212 "POST_upload_no_replace_queryarg",
1214 "There was already a child by that name, and you asked me "
1215 "to not replace it")
1216 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1217 d.addCallback(self.failUnlessIsBarDotTxt)
1220 def test_POST_upload_no_replace_field(self):
1221 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
1222 file=("bar.txt", self.NEWFILE_CONTENTS))
1223 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
1225 "There was already a child by that name, and you asked me "
1226 "to not replace it")
1227 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1228 d.addCallback(self.failUnlessIsBarDotTxt)
1231 def test_POST_upload_whendone(self):
1232 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
1233 file=("new.txt", self.NEWFILE_CONTENTS))
1234 d.addBoth(self.shouldRedirect, "/THERE")
1236 d.addCallback(lambda res:
1237 self.failUnlessChildContentsAre(fn, u"new.txt",
1238 self.NEWFILE_CONTENTS))
1241 def test_POST_upload_named(self):
1243 d = self.POST(self.public_url + "/foo", t="upload",
1244 name="new.txt", file=self.NEWFILE_CONTENTS)
1245 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1246 d.addCallback(lambda res:
1247 self.failUnlessChildContentsAre(fn, u"new.txt",
1248 self.NEWFILE_CONTENTS))
1251 def test_POST_upload_named_badfilename(self):
1252 d = self.POST(self.public_url + "/foo", t="upload",
1253 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
1254 d.addBoth(self.shouldFail, error.Error,
1255 "test_POST_upload_named_badfilename",
1257 "name= may not contain a slash",
1259 # make sure that nothing was added
1260 d.addCallback(lambda res:
1261 self.failUnlessNodeKeysAre(self._foo_node,
1262 [u"bar.txt", u"blockingfile",
1263 u"empty", u"n\u00fc.txt",
1267 def test_POST_mkdir(self): # return value?
1268 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
1269 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1270 d.addCallback(self.failUnlessNodeKeysAre, [])
1273 def test_POST_mkdir_no_parentdir_noredirect(self):
1274 d = self.POST("/uri?t=mkdir")
1275 def _after_mkdir(res):
1276 uri.NewDirectoryURI.init_from_string(res)
1277 d.addCallback(_after_mkdir)
1280 def test_POST_mkdir_no_parentdir_redirect(self):
1281 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
1282 d.addBoth(self.shouldRedirect, None, statuscode='303')
1283 def _check_target(target):
1284 target = urllib.unquote(target)
1285 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
1286 d.addCallback(_check_target)
1289 def test_POST_noparent_bad(self):
1290 d = self.shouldHTTPError2("POST /uri?t=bogus", 400, "Bad Request",
1291 "/uri accepts only PUT, PUT?t=mkdir, "
1292 "POST?t=upload, and POST?t=mkdir",
1293 self.POST, "/uri?t=bogus")
1296 def test_welcome_page_mkdir_button(self):
1297 # Fetch the welcome page.
1299 def _after_get_welcome_page(res):
1300 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)
1301 mo = MKDIR_BUTTON_RE.search(res)
1302 formaction = mo.group(1)
1304 formaname = mo.group(3)
1305 formavalue = mo.group(4)
1306 return (formaction, formt, formaname, formavalue)
1307 d.addCallback(_after_get_welcome_page)
1308 def _after_parse_form(res):
1309 (formaction, formt, formaname, formavalue) = res
1310 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
1311 d.addCallback(_after_parse_form)
1312 d.addBoth(self.shouldRedirect, None, statuscode='303')
1315 def test_POST_mkdir_replace(self): # return value?
1316 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
1317 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1318 d.addCallback(self.failUnlessNodeKeysAre, [])
1321 def test_POST_mkdir_no_replace_queryarg(self): # return value?
1322 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
1323 d.addBoth(self.shouldFail, error.Error,
1324 "POST_mkdir_no_replace_queryarg",
1326 "There was already a child by that name, and you asked me "
1327 "to not replace it")
1328 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1329 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1332 def test_POST_mkdir_no_replace_field(self): # return value?
1333 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
1335 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
1337 "There was already a child by that name, and you asked me "
1338 "to not replace it")
1339 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1340 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1343 def test_POST_mkdir_whendone_field(self):
1344 d = self.POST(self.public_url + "/foo",
1345 t="mkdir", name="newdir", when_done="/THERE")
1346 d.addBoth(self.shouldRedirect, "/THERE")
1347 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1348 d.addCallback(self.failUnlessNodeKeysAre, [])
1351 def test_POST_mkdir_whendone_queryarg(self):
1352 d = self.POST(self.public_url + "/foo?when_done=/THERE",
1353 t="mkdir", name="newdir")
1354 d.addBoth(self.shouldRedirect, "/THERE")
1355 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1356 d.addCallback(self.failUnlessNodeKeysAre, [])
1359 def test_POST_set_children(self):
1360 contents9, n9, newuri9 = self.makefile(9)
1361 contents10, n10, newuri10 = self.makefile(10)
1362 contents11, n11, newuri11 = self.makefile(11)
1365 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
1368 "ctime": 1002777696.7564139,
1369 "mtime": 1002777696.7564139
1372 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
1375 "ctime": 1002777696.7564139,
1376 "mtime": 1002777696.7564139
1379 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
1382 "ctime": 1002777696.7564139,
1383 "mtime": 1002777696.7564139
1386 }""" % (newuri9, newuri10, newuri11)
1388 url = self.webish_url + self.public_url + "/foo" + "?t=set_children"
1390 d = client.getPage(url, method="POST", postdata=reqbody)
1392 self.failUnlessURIMatchesChild(newuri9, self._foo_node, u"atomic_added_1")
1393 self.failUnlessURIMatchesChild(newuri10, self._foo_node, u"atomic_added_2")
1394 self.failUnlessURIMatchesChild(newuri11, self._foo_node, u"atomic_added_3")
1396 d.addCallback(_then)
1399 def test_POST_put_uri(self):
1400 contents, n, newuri = self.makefile(8)
1401 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
1402 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
1403 d.addCallback(lambda res:
1404 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1408 def test_POST_put_uri_replace(self):
1409 contents, n, newuri = self.makefile(8)
1410 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
1411 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
1412 d.addCallback(lambda res:
1413 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1417 def test_POST_put_uri_no_replace_queryarg(self):
1418 contents, n, newuri = self.makefile(8)
1419 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
1420 name="bar.txt", uri=newuri)
1421 d.addBoth(self.shouldFail, error.Error,
1422 "POST_put_uri_no_replace_queryarg",
1424 "There was already a child by that name, and you asked me "
1425 "to not replace it")
1426 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1427 d.addCallback(self.failUnlessIsBarDotTxt)
1430 def test_POST_put_uri_no_replace_field(self):
1431 contents, n, newuri = self.makefile(8)
1432 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
1433 name="bar.txt", uri=newuri)
1434 d.addBoth(self.shouldFail, error.Error,
1435 "POST_put_uri_no_replace_field",
1437 "There was already a child by that name, and you asked me "
1438 "to not replace it")
1439 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1440 d.addCallback(self.failUnlessIsBarDotTxt)
1443 def test_POST_delete(self):
1444 d = self.POST(self.public_url + "/foo", t="delete", name="bar.txt")
1445 d.addCallback(lambda res: self._foo_node.list())
1446 def _check(children):
1447 self.failIf(u"bar.txt" in children)
1448 d.addCallback(_check)
1451 def test_POST_rename_file(self):
1452 d = self.POST(self.public_url + "/foo", t="rename",
1453 from_name="bar.txt", to_name='wibble.txt')
1454 d.addCallback(lambda res:
1455 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1456 d.addCallback(lambda res:
1457 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
1458 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
1459 d.addCallback(self.failUnlessIsBarDotTxt)
1460 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
1461 d.addCallback(self.failUnlessIsBarJSON)
1464 def test_POST_rename_file_replace(self):
1465 # rename a file and replace a directory with it
1466 d = self.POST(self.public_url + "/foo", t="rename",
1467 from_name="bar.txt", to_name='empty')
1468 d.addCallback(lambda res:
1469 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1470 d.addCallback(lambda res:
1471 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
1472 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
1473 d.addCallback(self.failUnlessIsBarDotTxt)
1474 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
1475 d.addCallback(self.failUnlessIsBarJSON)
1478 def test_POST_rename_file_no_replace_queryarg(self):
1479 # rename a file and replace a directory with it
1480 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
1481 from_name="bar.txt", to_name='empty')
1482 d.addBoth(self.shouldFail, error.Error,
1483 "POST_rename_file_no_replace_queryarg",
1485 "There was already a child by that name, and you asked me "
1486 "to not replace it")
1487 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
1488 d.addCallback(self.failUnlessIsEmptyJSON)
1491 def test_POST_rename_file_no_replace_field(self):
1492 # rename a file and replace a directory with it
1493 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
1494 from_name="bar.txt", to_name='empty')
1495 d.addBoth(self.shouldFail, error.Error,
1496 "POST_rename_file_no_replace_field",
1498 "There was already a child by that name, and you asked me "
1499 "to not replace it")
1500 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
1501 d.addCallback(self.failUnlessIsEmptyJSON)
1504 def failUnlessIsEmptyJSON(self, res):
1505 data = simplejson.loads(res)
1506 self.failUnlessEqual(data[0], "dirnode", data)
1507 self.failUnlessEqual(len(data[1]["children"]), 0)
1509 def test_POST_rename_file_slash_fail(self):
1510 d = self.POST(self.public_url + "/foo", t="rename",
1511 from_name="bar.txt", to_name='kirk/spock.txt')
1512 d.addBoth(self.shouldFail, error.Error,
1513 "test_POST_rename_file_slash_fail",
1515 "to_name= may not contain a slash",
1517 d.addCallback(lambda res:
1518 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
1519 d.addCallback(lambda res: self.POST(self.public_url, t="rename",
1520 from_name="foo/bar.txt", to_name='george.txt'))
1521 d.addBoth(self.shouldFail, error.Error,
1522 "test_POST_rename_file_slash_fail",
1524 "from_name= may not contain a slash",
1526 d.addCallback(lambda res:
1527 self.failUnlessNodeHasChild(self.public_root, u"foo"))
1528 d.addCallback(lambda res:
1529 self.failIfNodeHasChild(self.public_root, u"george.txt"))
1530 d.addCallback(lambda res:
1531 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
1532 d.addCallback(lambda res: self.GET(self.public_url + "/foo?t=json"))
1533 d.addCallback(self.failUnlessIsFooJSON)
1536 def test_POST_rename_dir(self):
1537 d = self.POST(self.public_url, t="rename",
1538 from_name="foo", to_name='plunk')
1539 d.addCallback(lambda res:
1540 self.failIfNodeHasChild(self.public_root, u"foo"))
1541 d.addCallback(lambda res:
1542 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
1543 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
1544 d.addCallback(self.failUnlessIsFooJSON)
1547 def shouldRedirect(self, res, target=None, statuscode=None):
1548 """ If target is not None then the redirection has to go to target. If
1549 statuscode is not None then the redirection has to be accomplished with
1550 that HTTP status code."""
1551 if not isinstance(res, failure.Failure):
1552 self.fail("we were expecting to get redirected %s, not get an"
1553 " actual page: %s" % ((target is None) and "somewhere" or ("to " + target), res))
1554 res.trap(error.PageRedirect)
1555 if statuscode is not None:
1556 self.failUnlessEqual(res.value.status, statuscode)
1557 if target is not None:
1558 # the PageRedirect does not seem to capture the uri= query arg
1559 # properly, so we can't check for it.
1560 realtarget = self.webish_url + target
1561 self.failUnlessEqual(res.value.location, realtarget)
1562 return res.value.location
1564 def test_GET_URI_form(self):
1565 base = "/uri?uri=%s" % self._bar_txt_uri
1566 # this is supposed to give us a redirect to /uri/$URI, plus arguments
1567 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
1569 d.addBoth(self.shouldRedirect, targetbase)
1570 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
1571 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
1572 d.addCallback(lambda res: self.GET(base+"&t=json"))
1573 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
1574 d.addCallback(self.log, "about to get file by uri")
1575 d.addCallback(lambda res: self.GET(base, followRedirect=True))
1576 d.addCallback(self.failUnlessIsBarDotTxt)
1577 d.addCallback(self.log, "got file by uri, about to get dir by uri")
1578 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
1579 followRedirect=True))
1580 d.addCallback(self.failUnlessIsFooJSON)
1581 d.addCallback(self.log, "got dir by uri")
1585 def test_GET_rename_form(self):
1586 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
1587 followRedirect=True) # XXX [ ] todo: figure out why '.../foo' doesn't work
1589 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,))
1590 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
1591 d.addCallback(_check)
1594 def log(self, res, msg):
1595 #print "MSG: %s RES: %s" % (msg, res)
1599 def test_GET_URI_URL(self):
1600 base = "/uri/%s" % self._bar_txt_uri
1602 d.addCallback(self.failUnlessIsBarDotTxt)
1603 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
1604 d.addCallback(self.failUnlessIsBarDotTxt)
1605 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
1606 d.addCallback(self.failUnlessIsBarDotTxt)
1609 def test_GET_URI_URL_dir(self):
1610 base = "/uri/%s?t=json" % self._foo_uri
1612 d.addCallback(self.failUnlessIsFooJSON)
1615 def test_GET_URI_URL_missing(self):
1616 base = "/uri/%s" % self._bad_file_uri
1618 d.addBoth(self.shouldHTTPError, "test_GET_URI_URL_missing",
1619 http.GONE, response_substring="NotEnoughPeersError")
1620 # TODO: how can we exercise both sides of WebDownloadTarget.fail
1621 # here? we must arrange for a download to fail after target.open()
1622 # has been called, and then inspect the response to see that it is
1623 # shorter than we expected.
1626 def test_PUT_NEWFILEURL_uri(self):
1627 contents, n, new_uri = self.makefile(8)
1628 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
1629 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
1630 d.addCallback(lambda res:
1631 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1635 def test_PUT_NEWFILEURL_uri_replace(self):
1636 contents, n, new_uri = self.makefile(8)
1637 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
1638 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
1639 d.addCallback(lambda res:
1640 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1644 def test_PUT_NEWFILEURL_uri_no_replace(self):
1645 contents, n, new_uri = self.makefile(8)
1646 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
1647 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_uri_no_replace",
1649 "There was already a child by that name, and you asked me "
1650 "to not replace it")
1653 def test_PUT_NEWFILE_URI(self):
1654 file_contents = "New file contents here\n"
1655 d = self.PUT("/uri", file_contents)
1657 self.failUnless(uri in FakeCHKFileNode.all_contents)
1658 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
1660 return self.GET("/uri/%s" % uri)
1661 d.addCallback(_check)
1663 self.failUnlessEqual(res, file_contents)
1664 d.addCallback(_check2)
1667 def test_PUT_NEWFILE_URI_only_PUT(self):
1668 d = self.PUT("/uri?t=bogus", "")
1669 d.addBoth(self.shouldFail, error.Error,
1670 "PUT_NEWFILE_URI_only_PUT",
1672 "/uri only accepts PUT and PUT?t=mkdir")
1675 def test_PUT_NEWFILE_URI_mutable(self):
1676 file_contents = "New file contents here\n"
1677 d = self.PUT("/uri?mutable=true", file_contents)
1678 def _check_mutable(uri):
1681 self.failUnless(IMutableFileURI.providedBy(u))
1682 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
1683 n = self.s.create_node_from_uri(uri)
1684 return n.download_to_data()
1685 d.addCallback(_check_mutable)
1686 def _check2_mutable(data):
1687 self.failUnlessEqual(data, file_contents)
1688 d.addCallback(_check2_mutable)
1692 self.failUnless(uri in FakeCHKFileNode.all_contents)
1693 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
1695 return self.GET("/uri/%s" % uri)
1696 d.addCallback(_check)
1698 self.failUnlessEqual(res, file_contents)
1699 d.addCallback(_check2)
1702 def test_PUT_mkdir(self):
1703 d = self.PUT("/uri?t=mkdir", "")
1705 n = self.s.create_node_from_uri(uri.strip())
1706 d2 = self.failUnlessNodeKeysAre(n, [])
1707 d2.addCallback(lambda res:
1708 self.GET("/uri/%s?t=json" % uri))
1710 d.addCallback(_check)
1711 d.addCallback(self.failUnlessIsEmptyJSON)
1714 def test_POST_check(self):
1715 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
1717 # this returns a string form of the results, which are probably
1718 # None since we're using fake filenodes.
1719 # TODO: verify that the check actually happened, by changing
1720 # FakeCHKFileNode to count how many times .check() has been
1723 d.addCallback(_done)
1726 def test_bad_method(self):
1727 url = self.webish_url + self.public_url + "/foo/bar.txt"
1728 d = self.shouldHTTPError2("test_bad_method", 404, "Not Found", None,
1729 client.getPage, url, method="BOGUS")
1732 def test_short_url(self):
1733 url = self.webish_url + "/uri"
1734 d = self.shouldHTTPError2("test_short_url", 404, "Not Found", None,
1735 client.getPage, url, method="DELETE")