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):
89 def list_recent_uploads(self):
90 return self._all_upload_status
91 def list_recent_downloads(self):
92 return self._all_download_status
93 def list_recent_publish(self):
95 def list_recent_retrieve(self):
99 class WebMixin(object):
101 self.s = FakeClient()
102 self.s.startService()
103 self.ws = s = webish.WebishServer("0")
104 s.allow_local_access(True)
105 s.setServiceParent(self.s)
106 port = s.listener._port.getHost().port
107 self.webish_url = "http://localhost:%d" % port
109 l = [ self.s.create_empty_dirnode() for x in range(6) ]
110 d = defer.DeferredList(l)
112 self.public_root = res[0][1]
113 assert interfaces.IDirectoryNode.providedBy(self.public_root), res
114 self.public_url = "/uri/" + self.public_root.get_uri()
115 self.private_root = res[1][1]
119 self._foo_uri = foo.get_uri()
120 self._foo_readonly_uri = foo.get_readonly_uri()
121 # NOTE: we ignore the deferred on all set_uri() calls, because we
122 # know the fake nodes do these synchronously
123 self.public_root.set_uri(u"foo", foo.get_uri())
125 self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
126 foo.set_uri(u"bar.txt", self._bar_txt_uri)
128 foo.set_uri(u"empty", res[3][1].get_uri())
129 sub_uri = res[4][1].get_uri()
130 foo.set_uri(u"sub", sub_uri)
131 sub = self.s.create_node_from_uri(sub_uri)
133 _ign, n, blocking_uri = self.makefile(1)
134 foo.set_uri(u"blockingfile", blocking_uri)
136 unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
137 # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
138 # still think of it as an umlaut
139 foo.set_uri(unicode_filename, self._bar_txt_uri)
141 _ign, n, baz_file = self.makefile(2)
142 sub.set_uri(u"baz.txt", baz_file)
144 _ign, n, self._bad_file_uri = self.makefile(3)
145 # this uri should not be downloadable
146 del FakeCHKFileNode.all_contents[self._bad_file_uri]
149 self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri())
150 rodir.set_uri(u"nor", baz_file)
155 # public/foo/blockingfile
158 # public/foo/sub/baz.txt
160 # public/reedownlee/nor
161 self.NEWFILE_CONTENTS = "newfile contents\n"
163 return foo.get_metadata_for(u"bar.txt")
165 def _got_metadata(metadata):
166 self._bar_txt_metadata = metadata
167 d.addCallback(_got_metadata)
170 def makefile(self, number):
171 contents = "contents of file %s\n" % number
172 n = create_chk_filenode(self.s, contents)
173 return contents, n, n.get_uri()
176 return self.s.stopService()
178 def failUnlessIsBarDotTxt(self, res):
179 self.failUnlessEqual(res, self.BAR_CONTENTS, res)
181 def failUnlessIsBarJSON(self, res):
182 data = simplejson.loads(res)
183 self.failUnless(isinstance(data, list))
184 self.failUnlessEqual(data[0], "filenode")
185 self.failUnless(isinstance(data[1], dict))
186 self.failIf("rw_uri" in data[1]) # immutable
187 self.failUnlessEqual(data[1]["ro_uri"], self._bar_txt_uri)
188 self.failUnlessEqual(data[1]["size"], len(self.BAR_CONTENTS))
190 def failUnlessIsFooJSON(self, res):
191 data = simplejson.loads(res)
192 self.failUnless(isinstance(data, list))
193 self.failUnlessEqual(data[0], "dirnode", res)
194 self.failUnless(isinstance(data[1], dict))
195 self.failUnless("rw_uri" in data[1]) # mutable
196 self.failUnlessEqual(data[1]["rw_uri"], self._foo_uri)
197 self.failUnlessEqual(data[1]["ro_uri"], self._foo_readonly_uri)
199 kidnames = sorted(data[1]["children"])
200 self.failUnlessEqual(kidnames,
201 [u"bar.txt", u"blockingfile", u"empty",
202 u"n\u00fc.txt", u"sub"])
203 kids = data[1]["children"]
204 self.failUnlessEqual(kids[u"sub"][0], "dirnode")
205 self.failUnless("metadata" in kids[u"sub"][1])
206 self.failUnless("ctime" in kids[u"sub"][1]["metadata"])
207 self.failUnless("mtime" in kids[u"sub"][1]["metadata"])
208 self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
209 self.failUnlessEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
210 self.failUnlessEqual(kids[u"bar.txt"][1]["ro_uri"], self._bar_txt_uri)
211 self.failUnlessEqual(kids[u"bar.txt"][1]["metadata"]["ctime"],
212 self._bar_txt_metadata["ctime"])
213 self.failUnlessEqual(kids[u"n\u00fc.txt"][1]["ro_uri"],
216 def GET(self, urlpath, followRedirect=False):
217 url = self.webish_url + urlpath
218 return client.getPage(url, method="GET", followRedirect=followRedirect)
220 def PUT(self, urlpath, data):
221 url = self.webish_url + urlpath
222 return client.getPage(url, method="PUT", postdata=data)
224 def DELETE(self, urlpath):
225 url = self.webish_url + urlpath
226 return client.getPage(url, method="DELETE")
228 def POST(self, urlpath, followRedirect=False, **fields):
229 url = self.webish_url + urlpath
230 sepbase = "boogabooga"
234 form.append('Content-Disposition: form-data; name="_charset"')
238 for name, value in fields.iteritems():
239 if isinstance(value, tuple):
240 filename, value = value
241 form.append('Content-Disposition: form-data; name="%s"; '
242 'filename="%s"' % (name, filename.encode("utf-8")))
244 form.append('Content-Disposition: form-data; name="%s"' % name)
246 form.append(str(value))
249 body = "\r\n".join(form) + "\r\n"
250 headers = {"content-type": "multipart/form-data; boundary=%s" % sepbase,
252 return client.getPage(url, method="POST", postdata=body,
253 headers=headers, followRedirect=followRedirect)
255 def shouldFail(self, res, expected_failure, which,
256 substring=None, response_substring=None):
257 if isinstance(res, failure.Failure):
258 res.trap(expected_failure)
260 self.failUnless(substring in str(res),
261 "substring '%s' not in '%s'"
262 % (substring, str(res)))
263 if response_substring:
264 self.failUnless(response_substring in res.value.response,
265 "respose substring '%s' not in '%s'"
266 % (response_substring, res.value.response))
268 self.fail("%s was supposed to raise %s, not get '%s'" %
269 (which, expected_failure, res))
271 def shouldFail2(self, expected_failure, which, substring,
272 callable, *args, **kwargs):
273 assert substring is None or isinstance(substring, str)
274 d = defer.maybeDeferred(callable, *args, **kwargs)
276 if isinstance(res, failure.Failure):
277 res.trap(expected_failure)
279 self.failUnless(substring in str(res),
280 "substring '%s' not in '%s'"
281 % (substring, str(res)))
283 self.fail("%s was supposed to raise %s, not get '%s'" %
284 (which, expected_failure, res))
288 def should404(self, res, which):
289 if isinstance(res, failure.Failure):
290 res.trap(error.Error)
291 self.failUnlessEqual(res.value.status, "404")
293 self.fail("%s was supposed to Error(404), not get '%s'" %
296 def shouldHTTPError(self, res, which, code=None, substring=None,
297 response_substring=None):
298 if isinstance(res, failure.Failure):
299 res.trap(error.Error)
301 self.failUnlessEqual(res.value.status, str(code))
303 self.failUnless(substring in str(res),
304 "substring '%s' not in '%s'"
305 % (substring, str(res)))
306 if response_substring:
307 self.failUnless(response_substring in res.value.response,
308 "respose substring '%s' not in '%s'"
309 % (response_substring, res.value.response))
311 self.fail("%s was supposed to Error(%s), not get '%s'" %
314 def shouldHTTPError2(self, which,
315 code=None, substring=None, response_substring=None,
316 callable=None, *args, **kwargs):
317 assert substring is None or isinstance(substring, str)
319 d = defer.maybeDeferred(callable, *args, **kwargs)
320 d.addBoth(self.shouldHTTPError, which,
321 code, substring, response_substring)
325 class Web(WebMixin, unittest.TestCase):
326 def test_create(self):
329 def test_welcome(self):
332 self.failUnless('Welcome To AllMyData' in res)
333 self.failUnless('Tahoe' in res)
335 self.s.basedir = 'web/test_welcome'
336 fileutil.make_dirs("web/test_welcome")
337 fileutil.make_dirs("web/test_welcome/private")
339 d.addCallback(_check)
342 def test_provisioning_math(self):
343 self.failUnlessEqual(provisioning.binomial(10, 0), 1)
344 self.failUnlessEqual(provisioning.binomial(10, 1), 10)
345 self.failUnlessEqual(provisioning.binomial(10, 2), 45)
346 self.failUnlessEqual(provisioning.binomial(10, 9), 10)
347 self.failUnlessEqual(provisioning.binomial(10, 10), 1)
349 def test_provisioning(self):
350 d = self.GET("/provisioning/")
352 self.failUnless('Tahoe Provisioning Tool' in res)
353 fields = {'filled': True,
354 "num_users": int(50e3),
355 "files_per_user": 1000,
356 "space_per_user": int(1e9),
357 "sharing_ratio": 1.0,
358 "encoding_parameters": "3-of-10-5",
360 "ownership_mode": "A",
361 "download_rate": 100,
366 return self.POST("/provisioning/", **fields)
368 d.addCallback(_check)
370 self.failUnless('Tahoe Provisioning Tool' in res)
371 self.failUnless("Share space consumed: 167.01TB" in res)
373 fields = {'filled': True,
374 "num_users": int(50e6),
375 "files_per_user": 1000,
376 "space_per_user": int(5e9),
377 "sharing_ratio": 1.0,
378 "encoding_parameters": "25-of-100-50",
379 "num_servers": 30000,
380 "ownership_mode": "E",
381 "drive_failure_model": "U",
383 "download_rate": 1000,
388 return self.POST("/provisioning/", **fields)
389 d.addCallback(_check2)
391 self.failUnless("Share space consumed: huge!" in res)
392 fields = {'filled': True}
393 return self.POST("/provisioning/", **fields)
394 d.addCallback(_check3)
396 self.failUnless("Share space consumed:" in res)
397 d.addCallback(_check4)
400 def test_status(self):
401 dl_num = self.s.list_recent_downloads()[0].get_counter()
402 ul_num = self.s.list_recent_uploads()[0].get_counter()
403 d = self.GET("/status", followRedirect=True)
405 self.failUnless('Upload and Download Status' in res, res)
406 self.failUnless('"down-%d"' % dl_num in res, res)
407 self.failUnless('"up-%d"' % ul_num in res, res)
408 d.addCallback(_check)
409 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
411 self.failUnless("File Download Status" in res, res)
412 d.addCallback(_check_dl)
413 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
415 self.failUnless("File Upload Status" in res, res)
416 d.addCallback(_check_ul)
419 def test_status_numbers(self):
420 drrm = status.DownloadResultsRendererMixin()
421 self.failUnlessEqual(drrm.render_time(None, None), "")
422 self.failUnlessEqual(drrm.render_time(None, 2.5), "2.50s")
423 self.failUnlessEqual(drrm.render_time(None, 0.25), "250ms")
424 self.failUnlessEqual(drrm.render_time(None, 0.0021), "2.1ms")
425 self.failUnlessEqual(drrm.render_time(None, 0.000123), "123us")
426 self.failUnlessEqual(drrm.render_rate(None, None), "")
427 self.failUnlessEqual(drrm.render_rate(None, 2500000), "2.50MBps")
428 self.failUnlessEqual(drrm.render_rate(None, 30100), "30.1kBps")
429 self.failUnlessEqual(drrm.render_rate(None, 123), "123Bps")
431 urrm = status.UploadResultsRendererMixin()
432 self.failUnlessEqual(urrm.render_time(None, None), "")
433 self.failUnlessEqual(urrm.render_time(None, 2.5), "2.50s")
434 self.failUnlessEqual(urrm.render_time(None, 0.25), "250ms")
435 self.failUnlessEqual(urrm.render_time(None, 0.0021), "2.1ms")
436 self.failUnlessEqual(urrm.render_time(None, 0.000123), "123us")
437 self.failUnlessEqual(urrm.render_rate(None, None), "")
438 self.failUnlessEqual(urrm.render_rate(None, 2500000), "2.50MBps")
439 self.failUnlessEqual(urrm.render_rate(None, 30100), "30.1kBps")
440 self.failUnlessEqual(urrm.render_rate(None, 123), "123Bps")
442 def test_GET_FILEURL(self):
443 d = self.GET(self.public_url + "/foo/bar.txt")
444 d.addCallback(self.failUnlessIsBarDotTxt)
447 def test_GET_FILEURL_save(self):
448 d = self.GET(self.public_url + "/foo/bar.txt?save=bar.txt")
449 # TODO: look at the headers, expect a Content-Disposition: attachment
451 d.addCallback(self.failUnlessIsBarDotTxt)
454 def test_GET_FILEURL_download(self):
455 d = self.GET(self.public_url + "/foo/bar.txt?t=download")
456 d.addCallback(self.failUnlessIsBarDotTxt)
459 def test_GET_FILEURL_missing(self):
460 d = self.GET(self.public_url + "/foo/missing")
461 d.addBoth(self.should404, "test_GET_FILEURL_missing")
464 def test_PUT_NEWFILEURL(self):
465 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
466 # TODO: we lose the response code, so we can't check this
467 #self.failUnlessEqual(responsecode, 201)
468 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
469 d.addCallback(lambda res:
470 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
471 self.NEWFILE_CONTENTS))
474 def test_PUT_NEWFILEURL_replace(self):
475 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
476 # TODO: we lose the response code, so we can't check this
477 #self.failUnlessEqual(responsecode, 200)
478 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
479 d.addCallback(lambda res:
480 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
481 self.NEWFILE_CONTENTS))
484 def test_PUT_NEWFILEURL_no_replace(self):
485 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
486 self.NEWFILE_CONTENTS)
487 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
489 "There was already a child by that name, and you asked me "
493 def test_PUT_NEWFILEURL_mkdirs(self):
494 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
496 d.addCallback(self.failUnlessURIMatchesChild, fn, u"newdir/new.txt")
497 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
498 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
499 d.addCallback(lambda res:
500 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
501 self.NEWFILE_CONTENTS))
504 def test_PUT_NEWFILEURL_blocked(self):
505 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
506 self.NEWFILE_CONTENTS)
507 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
509 "cannot create directory because there is a file in the way")
512 def test_DELETE_FILEURL(self):
513 d = self.DELETE(self.public_url + "/foo/bar.txt")
514 d.addCallback(lambda res:
515 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
518 def test_DELETE_FILEURL_missing(self):
519 d = self.DELETE(self.public_url + "/foo/missing")
520 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
523 def test_DELETE_FILEURL_missing2(self):
524 d = self.DELETE(self.public_url + "/missing/missing")
525 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
528 def test_GET_FILEURL_json(self):
529 # twisted.web.http.parse_qs ignores any query args without an '=', so
530 # I can't do "GET /path?json", I have to do "GET /path/t=json"
531 # instead. This may make it tricky to emulate the S3 interface
533 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
534 d.addCallback(self.failUnlessIsBarJSON)
537 def test_GET_FILEURL_json_missing(self):
538 d = self.GET(self.public_url + "/foo/missing?json")
539 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
542 def disable_local_access(self, res=None):
543 self.ws.allow_local_access(False)
546 def test_GET_FILEURL_localfile(self):
547 localfile = os.path.abspath("web/GET_FILEURL_local file")
548 url = (self.public_url + "/foo/bar.txt?t=download&localfile=%s" %
549 urllib.quote(localfile))
550 fileutil.make_dirs("web")
553 self.failUnless(os.path.exists(localfile))
554 data = open(localfile, "rb").read()
555 self.failUnlessEqual(data, self.BAR_CONTENTS)
559 def test_GET_FILEURL_localfile_disabled(self):
560 localfile = os.path.abspath("web/GET_FILEURL_local file_disabled")
561 url = (self.public_url + "/foo/bar.txt?t=download&localfile=%s" %
562 urllib.quote(localfile))
563 fileutil.make_dirs("web")
564 self.disable_local_access()
566 d.addBoth(self.shouldFail, error.Error, "localfile disabled",
568 "local file access is disabled")
571 def test_GET_FILEURL_localfile_nonlocal(self):
572 # TODO: somehow pretend that we aren't local, and verify that the
573 # server refuses to write to local files, probably by changing the
574 # server's idea of what counts as "local".
575 old_LOCALHOST = webish.LOCALHOST
576 webish.LOCALHOST = "127.0.0.2"
577 localfile = os.path.abspath("web/GET_FILEURL_local file_nonlocal")
578 fileutil.make_dirs("web")
579 d = self.GET(self.public_url + "/foo/bar.txt?t=download&localfile=%s"
580 % urllib.quote(localfile))
581 d.addBoth(self.shouldFail, error.Error, "localfile non-local",
583 "localfile= or localdir= requires a local connection")
585 self.failIf(os.path.exists(localfile))
586 d.addCallback(_check)
588 webish.LOCALHOST = old_LOCALHOST
593 def test_GET_FILEURL_localfile_nonabsolute(self):
594 localfile = "web/nonabsolute/path"
595 fileutil.make_dirs("web/nonabsolute")
596 d = self.GET(self.public_url + "/foo/bar.txt?t=download&localfile=%s"
597 % urllib.quote(localfile))
598 d.addBoth(self.shouldFail, error.Error, "localfile non-absolute",
600 "localfile= or localdir= requires an absolute path")
602 self.failIf(os.path.exists(localfile))
603 d.addCallback(_check)
606 def test_PUT_NEWFILEURL_localfile(self):
607 localfile = os.path.abspath("web/PUT_NEWFILEURL_local file")
608 url = (self.public_url + "/foo/new.txt?t=upload&localfile=%s" %
609 urllib.quote(localfile))
610 fileutil.make_dirs("web")
611 f = open(localfile, "wb")
612 f.write(self.NEWFILE_CONTENTS)
614 d = self.PUT(url, "")
615 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
616 d.addCallback(lambda res:
617 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
618 self.NEWFILE_CONTENTS))
621 def test_PUT_NEWFILEURL_localfile_missingarg(self):
622 url = self.public_url + "/foo/new.txt?t=upload"
623 d = self.shouldHTTPError2("test_PUT_NEWFILEURL_localfile_missing",
625 "t=upload requires localfile= or localdir=",
629 def test_PUT_NEWFILEURL_localfile_disabled(self):
630 localfile = os.path.abspath("web/PUT_NEWFILEURL_local file_disabled")
631 url = (self.public_url + "/foo/new.txt?t=upload&localfile=%s" %
632 urllib.quote(localfile))
633 fileutil.make_dirs("web")
634 f = open(localfile, "wb")
635 f.write(self.NEWFILE_CONTENTS)
637 self.disable_local_access()
638 d = self.PUT(url, "")
639 d.addBoth(self.shouldFail, error.Error, "put localfile disabled",
641 "local file access is disabled")
644 def test_PUT_NEWFILEURL_localfile_mkdirs(self):
645 localfile = os.path.abspath("web/PUT_NEWFILEURL_local file_mkdirs")
646 fileutil.make_dirs("web")
647 f = open(localfile, "wb")
648 f.write(self.NEWFILE_CONTENTS)
650 d = self.PUT(self.public_url + "/foo/newdir/new.txt?t=upload&localfile=%s"
651 % urllib.quote(localfile), "")
652 d.addCallback(self.failUnlessURIMatchesChild,
653 self._foo_node, u"newdir/new.txt")
654 d.addCallback(lambda res:
655 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
656 d.addCallback(lambda res:
657 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
658 d.addCallback(lambda res:
659 self.failUnlessChildContentsAre(self._foo_node,
661 self.NEWFILE_CONTENTS))
664 def test_GET_FILEURL_uri(self):
665 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
667 self.failUnlessEqual(res, self._bar_txt_uri)
668 d.addCallback(_check)
669 d.addCallback(lambda res:
670 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
672 # for now, for files, uris and readonly-uris are the same
673 self.failUnlessEqual(res, self._bar_txt_uri)
674 d.addCallback(_check2)
677 def test_GET_FILEURL_badtype(self):
678 d = self.shouldHTTPError2("GET t=bogus", 400, "Bad Request",
681 self.public_url + "/foo/bar.txt?t=bogus")
684 def test_GET_FILEURL_uri_missing(self):
685 d = self.GET(self.public_url + "/foo/missing?t=uri")
686 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
689 def test_GET_DIRURL(self):
690 # the addSlash means we get a redirect here
691 d = self.GET(self.public_url + "/foo", followRedirect=True)
693 # the FILE reference points to a URI, but it should end in bar.txt
694 self.failUnless(re.search(r'<td>'
695 '<a href="[^"]+bar.txt">bar.txt</a>'
698 '\s+<td>%d</td>' % len(self.BAR_CONTENTS)
700 # the DIR reference just points to a URI
701 self.failUnless(re.search(r'<td><a href="/uri/URI%3ADIR2%3A[^"]+">sub</a></td>'
702 '\s+<td>DIR</td>', res))
703 d.addCallback(_check)
705 # look at a directory which is readonly
706 d.addCallback(lambda res:
707 self.GET(self.public_url + "/reedownlee", followRedirect=True))
709 self.failUnless("(readonly)" in res, res)
710 self.failIf("Upload a file" in res, res)
711 d.addCallback(_check2)
713 # and at a directory that contains a readonly directory
714 d.addCallback(lambda res:
715 self.GET(self.public_url, followRedirect=True))
717 self.failUnless(re.search(r'<td><a href="/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a>'
718 '</td>\s+<td>DIR-RO</td>', res))
719 d.addCallback(_check3)
723 def test_GET_DIRURL_badtype(self):
724 d = self.shouldHTTPError2("test_GET_DIRURL_badtype",
728 self.public_url + "/foo?t=bogus")
731 def test_GET_DIRURL_json(self):
732 d = self.GET(self.public_url + "/foo?t=json")
733 d.addCallback(self.failUnlessIsFooJSON)
736 def test_GET_DIRURL_manifest(self):
737 d = self.GET(self.public_url + "/foo?t=manifest", followRedirect=True)
739 self.failUnless("Refresh Capabilities" in manifest)
743 def test_GET_DIRURL_uri(self):
744 d = self.GET(self.public_url + "/foo?t=uri")
746 self.failUnlessEqual(res, self._foo_uri)
747 d.addCallback(_check)
750 def test_GET_DIRURL_readonly_uri(self):
751 d = self.GET(self.public_url + "/foo?t=readonly-uri")
753 self.failUnlessEqual(res, self._foo_readonly_uri)
754 d.addCallback(_check)
757 def test_PUT_NEWDIRURL(self):
758 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
759 d.addCallback(lambda res:
760 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
761 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
762 d.addCallback(self.failUnlessNodeKeysAre, [])
765 def test_PUT_NEWDIRURL_replace(self):
766 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
767 d.addCallback(lambda res:
768 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
769 d.addCallback(lambda res: self._foo_node.get(u"sub"))
770 d.addCallback(self.failUnlessNodeKeysAre, [])
773 def test_PUT_NEWDIRURL_no_replace(self):
774 d = self.PUT(self.public_url + "/foo/sub?t=mkdir&replace=false", "")
775 d.addBoth(self.shouldFail, error.Error, "PUT_NEWDIRURL_no_replace",
777 "There was already a child by that name, and you asked me "
779 d.addCallback(lambda res:
780 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
781 d.addCallback(lambda res: self._foo_node.get(u"sub"))
782 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
785 def test_PUT_NEWDIRURL_mkdir_p(self):
786 d = defer.succeed(None)
787 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
788 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
789 d.addCallback(lambda res: self._foo_node.get(u"mkp"))
790 def mkdir_p(mkpnode):
791 url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
793 def made_subsub(ssuri):
794 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
795 d.addCallback(lambda ssnode: self.failUnlessEqual(ssnode.get_uri(), ssuri))
797 d.addCallback(lambda uri2: self.failUnlessEqual(uri2, ssuri))
799 d.addCallback(made_subsub)
801 d.addCallback(mkdir_p)
804 def test_PUT_NEWDIRURL_mkdirs(self):
805 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
806 d.addCallback(lambda res:
807 self.failIfNodeHasChild(self._foo_node, u"newdir"))
808 d.addCallback(lambda res:
809 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
810 d.addCallback(lambda res:
811 self._foo_node.get_child_at_path(u"subdir/newdir"))
812 d.addCallback(self.failUnlessNodeKeysAre, [])
815 def test_DELETE_DIRURL(self):
816 d = self.DELETE(self.public_url + "/foo")
817 d.addCallback(lambda res:
818 self.failIfNodeHasChild(self.public_root, u"foo"))
821 def test_DELETE_DIRURL_missing(self):
822 d = self.DELETE(self.public_url + "/foo/missing")
823 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
824 d.addCallback(lambda res:
825 self.failUnlessNodeHasChild(self.public_root, u"foo"))
828 def test_DELETE_DIRURL_missing2(self):
829 d = self.DELETE(self.public_url + "/missing")
830 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
833 def test_walker(self):
835 def _visitor(path, node, metadata):
836 out.append((path, node))
837 return defer.succeed(None)
838 w = webish.DirnodeWalkerMixin()
839 d = w.walk(self.public_root, _visitor)
841 names = [path for (path,node) in out]
842 self.failUnlessEqual(sorted(names),
845 (u'foo',u'blockingfile'),
847 (u'foo', u"n\u00fc.txt"),
849 (u'foo',u'sub',u'baz.txt'),
851 (u'reedownlee', u'nor'),
853 subindex = names.index( (u'foo', u'sub') )
854 bazindex = names.index( (u'foo', u'sub', u'baz.txt') )
855 self.failUnless(subindex < bazindex)
856 for path,node in out:
857 if path[-1] in (u'bar.txt', u"n\u00fc.txt", u'blockingfile',
859 self.failUnless(interfaces.IFileNode.providedBy(node))
861 self.failUnless(interfaces.IDirectoryNode.providedBy(node))
862 d.addCallback(_check)
865 def test_GET_DIRURL_localdir(self):
866 localdir = os.path.abspath("web/GET_DIRURL_local dir")
867 fileutil.make_dirs("web")
868 d = self.GET(self.public_url + "/foo?t=download&localdir=%s" %
869 urllib.quote(localdir))
871 barfile = os.path.join(localdir, "bar.txt")
872 self.failUnless(os.path.exists(barfile))
873 data = open(barfile, "rb").read()
874 self.failUnlessEqual(data, self.BAR_CONTENTS)
875 blockingfile = os.path.join(localdir, "blockingfile")
876 self.failUnless(os.path.exists(blockingfile))
877 subdir = os.path.join(localdir, "sub")
878 self.failUnless(os.path.isdir(subdir))
879 d.addCallback(_check)
882 def test_GET_DIRURL_localdir_disabled(self):
883 localdir = os.path.abspath("web/GET_DIRURL_local dir_disabled")
884 fileutil.make_dirs("web")
885 self.disable_local_access()
886 d = self.GET(self.public_url + "/foo?t=download&localdir=%s" %
887 urllib.quote(localdir))
888 d.addBoth(self.shouldFail, error.Error, "localfile disabled",
890 "local file access is disabled")
893 def test_GET_DIRURL_localdir_nonabsolute(self):
894 localdir = "web/nonabsolute/dir path"
895 fileutil.make_dirs("web/nonabsolute")
896 d = self.GET(self.public_url + "/foo?t=download&localdir=%s" %
897 urllib.quote(localdir))
898 d.addBoth(self.shouldFail, error.Error, "localdir non-absolute",
900 "localfile= or localdir= requires an absolute path")
902 self.failIf(os.path.exists(localdir))
903 d.addCallback(_check)
906 def test_GET_DIRURL_localdir_nolocaldir(self):
907 d = self.shouldHTTPError2("GET_DIRURL_localdir_nolocaldir",
909 "t=download requires localdir=",
911 self.public_url + "/foo?t=download")
914 def touch(self, localdir, filename):
915 path = os.path.join(localdir, filename)
917 f.write("contents of %s\n" % filename)
922 w = webish.DirnodeWalkerMixin()
923 def visitor(childpath, childnode, metadata):
925 d = w.walk(self.public_root, visitor)
928 def failUnlessNodeKeysAre(self, node, expected_keys):
929 for k in expected_keys:
930 assert isinstance(k, unicode)
932 def _check(children):
933 self.failUnlessEqual(sorted(children.keys()), sorted(expected_keys))
934 d.addCallback(_check)
936 def failUnlessNodeHasChild(self, node, name):
937 assert isinstance(name, unicode)
939 def _check(children):
940 self.failUnless(name in children)
941 d.addCallback(_check)
943 def failIfNodeHasChild(self, node, name):
944 assert isinstance(name, unicode)
946 def _check(children):
947 self.failIf(name in children)
948 d.addCallback(_check)
951 def failUnlessChildContentsAre(self, node, name, expected_contents):
952 assert isinstance(name, unicode)
953 d = node.get_child_at_path(name)
954 d.addCallback(lambda node: node.download_to_data())
955 def _check(contents):
956 self.failUnlessEqual(contents, expected_contents)
957 d.addCallback(_check)
960 def failUnlessChildURIIs(self, node, name, expected_uri):
961 assert isinstance(name, unicode)
962 d = node.get_child_at_path(name)
964 self.failUnlessEqual(child.get_uri(), expected_uri.strip())
965 d.addCallback(_check)
968 def failUnlessURIMatchesChild(self, got_uri, node, name):
969 assert isinstance(name, unicode)
970 d = node.get_child_at_path(name)
972 self.failUnlessEqual(got_uri.strip(), child.get_uri())
973 d.addCallback(_check)
976 def failUnlessCHKURIHasContents(self, got_uri, contents):
977 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
979 def test_PUT_NEWDIRURL_localdir(self):
980 localdir = os.path.abspath("web/PUT_NEWDIRURL_local dir")
981 # create some files there
982 fileutil.make_dirs(os.path.join(localdir, "one"))
983 fileutil.make_dirs(os.path.join(localdir, "one/sub"))
984 fileutil.make_dirs(os.path.join(localdir, "two"))
985 fileutil.make_dirs(os.path.join(localdir, "three"))
986 self.touch(localdir, "three/foo.txt")
987 self.touch(localdir, "three/bar.txt")
988 self.touch(localdir, "zap.zip")
990 d = self.PUT(self.public_url + "/newdir?t=upload&localdir=%s"
991 % urllib.quote(localdir), "")
992 pr = self.public_root
993 d.addCallback(lambda res: self.failUnlessNodeHasChild(pr, u"newdir"))
994 d.addCallback(lambda res: pr.get(u"newdir"))
995 d.addCallback(self.failUnlessNodeKeysAre,
996 [u"one", u"two", u"three", u"zap.zip"])
997 d.addCallback(lambda res: pr.get_child_at_path(u"newdir/one"))
998 d.addCallback(self.failUnlessNodeKeysAre, [u"sub"])
999 d.addCallback(lambda res: pr.get_child_at_path(u"newdir/three"))
1000 d.addCallback(self.failUnlessNodeKeysAre, [u"foo.txt", u"bar.txt"])
1001 d.addCallback(lambda res: pr.get_child_at_path(u"newdir/three/bar.txt"))
1002 d.addCallback(lambda barnode: barnode.download_to_data())
1003 d.addCallback(lambda contents:
1004 self.failUnlessEqual(contents,
1005 "contents of three/bar.txt\n"))
1008 def test_PUT_NEWDIRURL_localdir_disabled(self):
1009 localdir = os.path.abspath("web/PUT_NEWDIRURL_local dir_disabled")
1010 # create some files there
1011 fileutil.make_dirs(os.path.join(localdir, "one"))
1012 fileutil.make_dirs(os.path.join(localdir, "one/sub"))
1013 fileutil.make_dirs(os.path.join(localdir, "two"))
1014 fileutil.make_dirs(os.path.join(localdir, "three"))
1015 self.touch(localdir, "three/foo.txt")
1016 self.touch(localdir, "three/bar.txt")
1017 self.touch(localdir, "zap.zip")
1019 self.disable_local_access()
1020 d = self.PUT(self.public_url + "/newdir?t=upload&localdir=%s"
1021 % urllib.quote(localdir), "")
1022 d.addBoth(self.shouldFail, error.Error, "localfile disabled",
1024 "local file access is disabled")
1027 def test_PUT_NEWDIRURL_localdir_mkdirs(self):
1028 localdir = os.path.abspath("web/PUT_NEWDIRURL_local dir_mkdirs")
1029 # create some files there
1030 fileutil.make_dirs(os.path.join(localdir, "one"))
1031 fileutil.make_dirs(os.path.join(localdir, "one/sub"))
1032 fileutil.make_dirs(os.path.join(localdir, "two"))
1033 fileutil.make_dirs(os.path.join(localdir, "three"))
1034 self.touch(localdir, "three/foo.txt")
1035 self.touch(localdir, "three/bar.txt")
1036 self.touch(localdir, "zap.zip")
1038 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=upload&localdir=%s"
1039 % urllib.quote(localdir),
1042 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"subdir"))
1043 d.addCallback(lambda res: fn.get_child_at_path(u"subdir/newdir"))
1044 d.addCallback(self.failUnlessNodeKeysAre,
1045 [u"one", u"two", u"three", u"zap.zip"])
1046 d.addCallback(lambda res: fn.get_child_at_path(u"subdir/newdir/one"))
1047 d.addCallback(self.failUnlessNodeKeysAre, [u"sub"])
1048 d.addCallback(lambda res: fn.get_child_at_path(u"subdir/newdir/three"))
1049 d.addCallback(self.failUnlessNodeKeysAre, [u"foo.txt", u"bar.txt"])
1050 d.addCallback(lambda res:
1051 fn.get_child_at_path(u"subdir/newdir/three/bar.txt"))
1052 d.addCallback(lambda barnode: barnode.download_to_data())
1053 d.addCallback(lambda contents:
1054 self.failUnlessEqual(contents,
1055 "contents of three/bar.txt\n"))
1058 def test_PUT_NEWDIRURL_localdir_missing(self):
1059 localdir = os.path.abspath("web/PUT_NEWDIRURL_localdir_missing")
1060 # we do *not* create it, to trigger an error
1061 url = (self.public_url + "/foo/subdir/newdir?t=upload&localdir=%s"
1062 % urllib.quote(localdir))
1063 d = self.shouldHTTPError2("test_PUT_NEWDIRURL_localdir_missing",
1065 "%s doesn't exist!" % localdir,
1069 def test_POST_upload(self):
1070 d = self.POST(self.public_url + "/foo", t="upload",
1071 file=("new.txt", self.NEWFILE_CONTENTS))
1073 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1074 d.addCallback(lambda res:
1075 self.failUnlessChildContentsAre(fn, u"new.txt",
1076 self.NEWFILE_CONTENTS))
1079 def test_POST_upload_unicode(self):
1080 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1081 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1082 d = self.POST(self.public_url + "/foo", t="upload",
1083 file=(filename, self.NEWFILE_CONTENTS))
1085 d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1086 d.addCallback(lambda res:
1087 self.failUnlessChildContentsAre(fn, filename,
1088 self.NEWFILE_CONTENTS))
1089 d.addCallback(lambda res: self.GET(target_url))
1090 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1091 self.NEWFILE_CONTENTS,
1095 def test_POST_upload_no_link(self):
1096 d = self.POST("/uri", t="upload",
1097 file=("new.txt", self.NEWFILE_CONTENTS))
1098 def _check_upload_results(page):
1099 # this should be a page which describes the results of the upload
1100 # that just finished.
1101 self.failUnless("Upload Results:" in page)
1102 self.failUnless("URI:" in page)
1103 uri_re = re.compile("URI: <tt><span>(.*)</span>")
1104 mo = uri_re.search(page)
1105 self.failUnless(mo, page)
1106 new_uri = mo.group(1)
1108 d.addCallback(_check_upload_results)
1109 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1112 def test_POST_upload_no_link_whendone(self):
1113 d = self.POST("/uri", t="upload", when_done="/",
1114 file=("new.txt", self.NEWFILE_CONTENTS))
1115 d.addBoth(self.shouldRedirect, "/")
1118 def test_POST_upload_no_link_mutable(self):
1119 d = self.POST("/uri", t="upload", mutable="true",
1120 file=("new.txt", self.NEWFILE_CONTENTS))
1121 def _check(new_uri):
1122 new_uri = new_uri.strip()
1124 self.failUnless(IMutableFileURI.providedBy(u))
1125 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
1126 n = self.s.create_node_from_uri(new_uri)
1127 return n.download_to_data()
1128 d.addCallback(_check)
1130 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1131 d.addCallback(_check2)
1134 def test_POST_upload_mutable(self):
1135 # this creates a mutable file
1136 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
1137 file=("new.txt", self.NEWFILE_CONTENTS))
1139 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1140 d.addCallback(lambda res:
1141 self.failUnlessChildContentsAre(fn, u"new.txt",
1142 self.NEWFILE_CONTENTS))
1143 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1145 self.failUnless(IMutableFileNode.providedBy(newnode))
1146 self.failUnless(newnode.is_mutable())
1147 self.failIf(newnode.is_readonly())
1148 self._mutable_uri = newnode.get_uri()
1151 # now upload it again and make sure that the URI doesn't change
1152 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
1153 d.addCallback(lambda res:
1154 self.POST(self.public_url + "/foo", t="upload",
1156 file=("new.txt", NEWER_CONTENTS)))
1157 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1158 d.addCallback(lambda res:
1159 self.failUnlessChildContentsAre(fn, u"new.txt",
1161 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1163 self.failUnless(IMutableFileNode.providedBy(newnode))
1164 self.failUnless(newnode.is_mutable())
1165 self.failIf(newnode.is_readonly())
1166 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1167 d.addCallback(_got2)
1169 # finally list the directory, since mutable files are displayed
1172 d.addCallback(lambda res:
1173 self.GET(self.public_url + "/foo",
1174 followRedirect=True))
1175 def _check_page(res):
1176 # TODO: assert more about the contents
1177 self.failUnless("Overwrite" in res)
1178 self.failUnless("Choose new file:" in res)
1180 d.addCallback(_check_page)
1182 # test that clicking on the "overwrite" button works
1183 EVEN_NEWER_CONTENTS = NEWER_CONTENTS + "even newer\n"
1184 def _parse_overwrite_form_and_submit(res):
1185 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)
1186 mo = OVERWRITE_FORM_RE.search(res)
1188 formaction=mo.group(1)
1189 formname=mo.group(2)
1190 formwhendone=mo.group(3)
1192 if formaction == ".":
1193 formaction = self.public_url + "/foo"
1194 return self.POST(formaction, t="overwrite", name=formname, when_done=formwhendone, file=("new.txt", EVEN_NEWER_CONTENTS), followRedirect=False)
1195 d.addCallback(_parse_overwrite_form_and_submit)
1196 d.addBoth(self.shouldRedirect, urllib.quote(self.public_url + "/foo/"))
1197 d.addCallback(lambda res:
1198 self.failUnlessChildContentsAre(fn, u"new.txt",
1199 EVEN_NEWER_CONTENTS))
1200 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1202 self.failUnless(IMutableFileNode.providedBy(newnode))
1203 self.failUnless(newnode.is_mutable())
1204 self.failIf(newnode.is_readonly())
1205 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1206 d.addCallback(_got3)
1210 def test_POST_upload_replace(self):
1211 d = self.POST(self.public_url + "/foo", t="upload",
1212 file=("bar.txt", self.NEWFILE_CONTENTS))
1214 d.addCallback(self.failUnlessURIMatchesChild, fn, u"bar.txt")
1215 d.addCallback(lambda res:
1216 self.failUnlessChildContentsAre(fn, u"bar.txt",
1217 self.NEWFILE_CONTENTS))
1220 def test_POST_upload_no_replace_ok(self):
1221 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1222 file=("new.txt", self.NEWFILE_CONTENTS))
1223 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
1224 d.addCallback(lambda res: self.failUnlessEqual(res,
1225 self.NEWFILE_CONTENTS))
1228 def test_POST_upload_no_replace_queryarg(self):
1229 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1230 file=("bar.txt", self.NEWFILE_CONTENTS))
1231 d.addBoth(self.shouldFail, error.Error,
1232 "POST_upload_no_replace_queryarg",
1234 "There was already a child by that name, and you asked me "
1235 "to not replace it")
1236 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1237 d.addCallback(self.failUnlessIsBarDotTxt)
1240 def test_POST_upload_no_replace_field(self):
1241 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
1242 file=("bar.txt", self.NEWFILE_CONTENTS))
1243 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
1245 "There was already a child by that name, and you asked me "
1246 "to not replace it")
1247 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1248 d.addCallback(self.failUnlessIsBarDotTxt)
1251 def test_POST_upload_whendone(self):
1252 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
1253 file=("new.txt", self.NEWFILE_CONTENTS))
1254 d.addBoth(self.shouldRedirect, "/THERE")
1256 d.addCallback(lambda res:
1257 self.failUnlessChildContentsAre(fn, u"new.txt",
1258 self.NEWFILE_CONTENTS))
1261 def test_POST_upload_named(self):
1263 d = self.POST(self.public_url + "/foo", t="upload",
1264 name="new.txt", file=self.NEWFILE_CONTENTS)
1265 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1266 d.addCallback(lambda res:
1267 self.failUnlessChildContentsAre(fn, u"new.txt",
1268 self.NEWFILE_CONTENTS))
1271 def test_POST_upload_named_badfilename(self):
1272 d = self.POST(self.public_url + "/foo", t="upload",
1273 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
1274 d.addBoth(self.shouldFail, error.Error,
1275 "test_POST_upload_named_badfilename",
1277 "name= may not contain a slash",
1279 # make sure that nothing was added
1280 d.addCallback(lambda res:
1281 self.failUnlessNodeKeysAre(self._foo_node,
1282 [u"bar.txt", u"blockingfile",
1283 u"empty", u"n\u00fc.txt",
1287 def test_POST_mkdir(self): # return value?
1288 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
1289 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1290 d.addCallback(self.failUnlessNodeKeysAre, [])
1293 def test_POST_mkdir_no_parentdir_noredirect(self):
1294 d = self.POST("/uri?t=mkdir")
1295 def _after_mkdir(res):
1296 uri.NewDirectoryURI.init_from_string(res)
1297 d.addCallback(_after_mkdir)
1300 def test_POST_mkdir_no_parentdir_redirect(self):
1301 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
1302 d.addBoth(self.shouldRedirect, None, statuscode='303')
1303 def _check_target(target):
1304 target = urllib.unquote(target)
1305 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
1306 d.addCallback(_check_target)
1309 def test_POST_noparent_bad(self):
1310 d = self.shouldHTTPError2("POST /uri?t=bogus", 400, "Bad Request",
1311 "/uri accepts only PUT, PUT?t=mkdir, "
1312 "POST?t=upload, and POST?t=mkdir",
1313 self.POST, "/uri?t=bogus")
1316 def test_welcome_page_mkdir_button(self):
1317 # Fetch the welcome page.
1319 def _after_get_welcome_page(res):
1320 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)
1321 mo = MKDIR_BUTTON_RE.search(res)
1322 formaction = mo.group(1)
1324 formaname = mo.group(3)
1325 formavalue = mo.group(4)
1326 return (formaction, formt, formaname, formavalue)
1327 d.addCallback(_after_get_welcome_page)
1328 def _after_parse_form(res):
1329 (formaction, formt, formaname, formavalue) = res
1330 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
1331 d.addCallback(_after_parse_form)
1332 d.addBoth(self.shouldRedirect, None, statuscode='303')
1335 def test_POST_mkdir_replace(self): # return value?
1336 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
1337 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1338 d.addCallback(self.failUnlessNodeKeysAre, [])
1341 def test_POST_mkdir_no_replace_queryarg(self): # return value?
1342 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
1343 d.addBoth(self.shouldFail, error.Error,
1344 "POST_mkdir_no_replace_queryarg",
1346 "There was already a child by that name, and you asked me "
1347 "to not replace it")
1348 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1349 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1352 def test_POST_mkdir_no_replace_field(self): # return value?
1353 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
1355 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
1357 "There was already a child by that name, and you asked me "
1358 "to not replace it")
1359 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1360 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1363 def test_POST_mkdir_whendone_field(self):
1364 d = self.POST(self.public_url + "/foo",
1365 t="mkdir", name="newdir", when_done="/THERE")
1366 d.addBoth(self.shouldRedirect, "/THERE")
1367 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1368 d.addCallback(self.failUnlessNodeKeysAre, [])
1371 def test_POST_mkdir_whendone_queryarg(self):
1372 d = self.POST(self.public_url + "/foo?when_done=/THERE",
1373 t="mkdir", name="newdir")
1374 d.addBoth(self.shouldRedirect, "/THERE")
1375 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1376 d.addCallback(self.failUnlessNodeKeysAre, [])
1379 def test_POST_set_children(self):
1380 contents9, n9, newuri9 = self.makefile(9)
1381 contents10, n10, newuri10 = self.makefile(10)
1382 contents11, n11, newuri11 = self.makefile(11)
1385 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
1388 "ctime": 1002777696.7564139,
1389 "mtime": 1002777696.7564139
1392 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
1395 "ctime": 1002777696.7564139,
1396 "mtime": 1002777696.7564139
1399 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
1402 "ctime": 1002777696.7564139,
1403 "mtime": 1002777696.7564139
1406 }""" % (newuri9, newuri10, newuri11)
1408 url = self.webish_url + self.public_url + "/foo" + "?t=set_children"
1410 d = client.getPage(url, method="POST", postdata=reqbody)
1412 self.failUnlessURIMatchesChild(newuri9, self._foo_node, u"atomic_added_1")
1413 self.failUnlessURIMatchesChild(newuri10, self._foo_node, u"atomic_added_2")
1414 self.failUnlessURIMatchesChild(newuri11, self._foo_node, u"atomic_added_3")
1416 d.addCallback(_then)
1419 def test_POST_put_uri(self):
1420 contents, n, newuri = self.makefile(8)
1421 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
1422 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
1423 d.addCallback(lambda res:
1424 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1428 def test_POST_put_uri_replace(self):
1429 contents, n, newuri = self.makefile(8)
1430 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
1431 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
1432 d.addCallback(lambda res:
1433 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1437 def test_POST_put_uri_no_replace_queryarg(self):
1438 contents, n, newuri = self.makefile(8)
1439 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
1440 name="bar.txt", uri=newuri)
1441 d.addBoth(self.shouldFail, error.Error,
1442 "POST_put_uri_no_replace_queryarg",
1444 "There was already a child by that name, and you asked me "
1445 "to not replace it")
1446 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1447 d.addCallback(self.failUnlessIsBarDotTxt)
1450 def test_POST_put_uri_no_replace_field(self):
1451 contents, n, newuri = self.makefile(8)
1452 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
1453 name="bar.txt", uri=newuri)
1454 d.addBoth(self.shouldFail, error.Error,
1455 "POST_put_uri_no_replace_field",
1457 "There was already a child by that name, and you asked me "
1458 "to not replace it")
1459 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1460 d.addCallback(self.failUnlessIsBarDotTxt)
1463 def test_POST_delete(self):
1464 d = self.POST(self.public_url + "/foo", t="delete", name="bar.txt")
1465 d.addCallback(lambda res: self._foo_node.list())
1466 def _check(children):
1467 self.failIf(u"bar.txt" in children)
1468 d.addCallback(_check)
1471 def test_POST_rename_file(self):
1472 d = self.POST(self.public_url + "/foo", t="rename",
1473 from_name="bar.txt", to_name='wibble.txt')
1474 d.addCallback(lambda res:
1475 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1476 d.addCallback(lambda res:
1477 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
1478 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
1479 d.addCallback(self.failUnlessIsBarDotTxt)
1480 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
1481 d.addCallback(self.failUnlessIsBarJSON)
1484 def test_POST_rename_file_replace(self):
1485 # rename a file and replace a directory with it
1486 d = self.POST(self.public_url + "/foo", t="rename",
1487 from_name="bar.txt", to_name='empty')
1488 d.addCallback(lambda res:
1489 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1490 d.addCallback(lambda res:
1491 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
1492 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
1493 d.addCallback(self.failUnlessIsBarDotTxt)
1494 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
1495 d.addCallback(self.failUnlessIsBarJSON)
1498 def test_POST_rename_file_no_replace_queryarg(self):
1499 # rename a file and replace a directory with it
1500 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
1501 from_name="bar.txt", to_name='empty')
1502 d.addBoth(self.shouldFail, error.Error,
1503 "POST_rename_file_no_replace_queryarg",
1505 "There was already a child by that name, and you asked me "
1506 "to not replace it")
1507 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
1508 d.addCallback(self.failUnlessIsEmptyJSON)
1511 def test_POST_rename_file_no_replace_field(self):
1512 # rename a file and replace a directory with it
1513 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
1514 from_name="bar.txt", to_name='empty')
1515 d.addBoth(self.shouldFail, error.Error,
1516 "POST_rename_file_no_replace_field",
1518 "There was already a child by that name, and you asked me "
1519 "to not replace it")
1520 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
1521 d.addCallback(self.failUnlessIsEmptyJSON)
1524 def failUnlessIsEmptyJSON(self, res):
1525 data = simplejson.loads(res)
1526 self.failUnlessEqual(data[0], "dirnode", data)
1527 self.failUnlessEqual(len(data[1]["children"]), 0)
1529 def test_POST_rename_file_slash_fail(self):
1530 d = self.POST(self.public_url + "/foo", t="rename",
1531 from_name="bar.txt", to_name='kirk/spock.txt')
1532 d.addBoth(self.shouldFail, error.Error,
1533 "test_POST_rename_file_slash_fail",
1535 "to_name= may not contain a slash",
1537 d.addCallback(lambda res:
1538 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
1539 d.addCallback(lambda res: self.POST(self.public_url, t="rename",
1540 from_name="foo/bar.txt", to_name='george.txt'))
1541 d.addBoth(self.shouldFail, error.Error,
1542 "test_POST_rename_file_slash_fail",
1544 "from_name= may not contain a slash",
1546 d.addCallback(lambda res:
1547 self.failUnlessNodeHasChild(self.public_root, u"foo"))
1548 d.addCallback(lambda res:
1549 self.failIfNodeHasChild(self.public_root, u"george.txt"))
1550 d.addCallback(lambda res:
1551 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
1552 d.addCallback(lambda res: self.GET(self.public_url + "/foo?t=json"))
1553 d.addCallback(self.failUnlessIsFooJSON)
1556 def test_POST_rename_dir(self):
1557 d = self.POST(self.public_url, t="rename",
1558 from_name="foo", to_name='plunk')
1559 d.addCallback(lambda res:
1560 self.failIfNodeHasChild(self.public_root, u"foo"))
1561 d.addCallback(lambda res:
1562 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
1563 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
1564 d.addCallback(self.failUnlessIsFooJSON)
1567 def shouldRedirect(self, res, target=None, statuscode=None):
1568 """ If target is not None then the redirection has to go to target. If
1569 statuscode is not None then the redirection has to be accomplished with
1570 that HTTP status code."""
1571 if not isinstance(res, failure.Failure):
1572 self.fail("we were expecting to get redirected %s, not get an"
1573 " actual page: %s" % ((target is None) and "somewhere" or ("to " + target), res))
1574 res.trap(error.PageRedirect)
1575 if statuscode is not None:
1576 self.failUnlessEqual(res.value.status, statuscode)
1577 if target is not None:
1578 # the PageRedirect does not seem to capture the uri= query arg
1579 # properly, so we can't check for it.
1580 realtarget = self.webish_url + target
1581 self.failUnlessEqual(res.value.location, realtarget)
1582 return res.value.location
1584 def test_GET_URI_form(self):
1585 base = "/uri?uri=%s" % self._bar_txt_uri
1586 # this is supposed to give us a redirect to /uri/$URI, plus arguments
1587 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
1589 d.addBoth(self.shouldRedirect, targetbase)
1590 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
1591 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
1592 d.addCallback(lambda res: self.GET(base+"&t=json"))
1593 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
1594 d.addCallback(self.log, "about to get file by uri")
1595 d.addCallback(lambda res: self.GET(base, followRedirect=True))
1596 d.addCallback(self.failUnlessIsBarDotTxt)
1597 d.addCallback(self.log, "got file by uri, about to get dir by uri")
1598 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
1599 followRedirect=True))
1600 d.addCallback(self.failUnlessIsFooJSON)
1601 d.addCallback(self.log, "got dir by uri")
1605 def test_GET_rename_form(self):
1606 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
1607 followRedirect=True) # XXX [ ] todo: figure out why '.../foo' doesn't work
1609 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,))
1610 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
1611 d.addCallback(_check)
1614 def log(self, res, msg):
1615 #print "MSG: %s RES: %s" % (msg, res)
1619 def test_GET_URI_URL(self):
1620 base = "/uri/%s" % self._bar_txt_uri
1622 d.addCallback(self.failUnlessIsBarDotTxt)
1623 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
1624 d.addCallback(self.failUnlessIsBarDotTxt)
1625 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
1626 d.addCallback(self.failUnlessIsBarDotTxt)
1629 def test_GET_URI_URL_dir(self):
1630 base = "/uri/%s?t=json" % self._foo_uri
1632 d.addCallback(self.failUnlessIsFooJSON)
1635 def test_GET_URI_URL_missing(self):
1636 base = "/uri/%s" % self._bad_file_uri
1638 d.addBoth(self.shouldHTTPError, "test_GET_URI_URL_missing",
1639 http.GONE, response_substring="NotEnoughPeersError")
1640 # TODO: how can we exercise both sides of WebDownloadTarget.fail
1641 # here? we must arrange for a download to fail after target.open()
1642 # has been called, and then inspect the response to see that it is
1643 # shorter than we expected.
1646 def test_PUT_NEWFILEURL_uri(self):
1647 contents, n, new_uri = self.makefile(8)
1648 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
1649 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
1650 d.addCallback(lambda res:
1651 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1655 def test_PUT_NEWFILEURL_uri_replace(self):
1656 contents, n, new_uri = self.makefile(8)
1657 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
1658 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
1659 d.addCallback(lambda res:
1660 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1664 def test_PUT_NEWFILEURL_uri_no_replace(self):
1665 contents, n, new_uri = self.makefile(8)
1666 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
1667 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_uri_no_replace",
1669 "There was already a child by that name, and you asked me "
1670 "to not replace it")
1673 def test_PUT_NEWFILE_URI(self):
1674 file_contents = "New file contents here\n"
1675 d = self.PUT("/uri", file_contents)
1677 self.failUnless(uri in FakeCHKFileNode.all_contents)
1678 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
1680 return self.GET("/uri/%s" % uri)
1681 d.addCallback(_check)
1683 self.failUnlessEqual(res, file_contents)
1684 d.addCallback(_check2)
1687 def test_PUT_NEWFILE_URI_only_PUT(self):
1688 d = self.PUT("/uri?t=bogus", "")
1689 d.addBoth(self.shouldFail, error.Error,
1690 "PUT_NEWFILE_URI_only_PUT",
1692 "/uri only accepts PUT and PUT?t=mkdir")
1695 def test_PUT_NEWFILE_URI_mutable(self):
1696 file_contents = "New file contents here\n"
1697 d = self.PUT("/uri?mutable=true", file_contents)
1698 def _check_mutable(uri):
1701 self.failUnless(IMutableFileURI.providedBy(u))
1702 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
1703 n = self.s.create_node_from_uri(uri)
1704 return n.download_to_data()
1705 d.addCallback(_check_mutable)
1706 def _check2_mutable(data):
1707 self.failUnlessEqual(data, file_contents)
1708 d.addCallback(_check2_mutable)
1712 self.failUnless(uri in FakeCHKFileNode.all_contents)
1713 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
1715 return self.GET("/uri/%s" % uri)
1716 d.addCallback(_check)
1718 self.failUnlessEqual(res, file_contents)
1719 d.addCallback(_check2)
1722 def test_PUT_mkdir(self):
1723 d = self.PUT("/uri?t=mkdir", "")
1725 n = self.s.create_node_from_uri(uri.strip())
1726 d2 = self.failUnlessNodeKeysAre(n, [])
1727 d2.addCallback(lambda res:
1728 self.GET("/uri/%s?t=json" % uri))
1730 d.addCallback(_check)
1731 d.addCallback(self.failUnlessIsEmptyJSON)
1734 def test_POST_check(self):
1735 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
1737 # this returns a string form of the results, which are probably
1738 # None since we're using fake filenodes.
1739 # TODO: verify that the check actually happened, by changing
1740 # FakeCHKFileNode to count how many times .check() has been
1743 d.addCallback(_done)
1746 def test_bad_method(self):
1747 url = self.webish_url + self.public_url + "/foo/bar.txt"
1748 d = self.shouldHTTPError2("test_bad_method", 404, "Not Found", None,
1749 client.getPage, url, method="BOGUS")
1752 def test_short_url(self):
1753 url = self.webish_url + "/uri"
1754 d = self.shouldHTTPError2("test_short_url", 404, "Not Found", None,
1755 client.getPage, url, method="DELETE")