]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/test/test_web.py
webish: this file is too big, start breaking it into pieces, beginning with status
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / test / test_web.py
1 import re, os.path, urllib
2 import simplejson
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
13
14 # create a fake uploader/downloader, and a couple of fake dirnodes, then
15 # create a webserver that works against them
16
17 class FakeIntroducerClient:
18     def get_all_connectors(self):
19         return {}
20     def get_all_connections_for(self, service_name):
21         return frozenset()
22     def get_all_peerids(self):
23         return frozenset()
24
25 class FakeClient(service.MultiService):
26     nodeid = "fake_nodeid"
27     basedir = "fake_basedir"
28     def get_versions(self):
29         return {'allmydata': "fake",
30                 'foolscap': "fake",
31                 'twisted': "fake",
32                 'zfec': "fake",
33                 }
34     introducer_furl = "None"
35     introducer_client = FakeIntroducerClient()
36     _all_upload_status = [upload.UploadStatus()]
37     _all_download_status = [download.DownloadStatus()]
38
39     def connected_to_introducer(self):
40         return False
41
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)
51
52     def create_empty_dirnode(self):
53         n = FakeDirectoryNode(self)
54         d = n.create()
55         d.addCallback(lambda res: n)
56         return d
57
58     def create_mutable_file(self, contents=""):
59         n = FakeMutableFileNode(self)
60         return n.create(contents)
61
62     def upload(self, uploadable):
63         d = uploadable.get_size()
64         d.addCallback(lambda size: uploadable.read(size))
65         def _got_data(datav):
66             data = "".join(datav)
67             n = create_chk_filenode(self, data)
68             results = upload.UploadResults()
69             results.uri = n.get_uri()
70             return results
71         d.addCallback(_got_data)
72         return d
73
74     def list_all_uploads(self):
75         return []
76     def list_all_downloads(self):
77         return []
78
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):
84         return []
85     def list_active_retrieve(self):
86         return []
87
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):
93         return []
94     def list_recent_retrieve(self):
95         return []
96
97
98 class WebMixin(object):
99     def setUp(self):
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
107
108         l = [ self.s.create_empty_dirnode() for x in range(6) ]
109         d = defer.DeferredList(l)
110         def _then(res):
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]
115
116             foo = res[2][1]
117             self._foo_node = foo
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())
123
124             self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
125             foo.set_uri(u"bar.txt", self._bar_txt_uri)
126
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)
131
132             _ign, n, blocking_uri = self.makefile(1)
133             foo.set_uri(u"blockingfile", blocking_uri)
134
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)
139
140             _ign, n, baz_file = self.makefile(2)
141             sub.set_uri(u"baz.txt", baz_file)
142
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]
146
147             rodir = res[5][1]
148             self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri())
149             rodir.set_uri(u"nor", baz_file)
150
151             # public/
152             # public/foo/
153             # public/foo/bar.txt
154             # public/foo/blockingfile
155             # public/foo/empty/
156             # public/foo/sub/
157             # public/foo/sub/baz.txt
158             # public/reedownlee/
159             # public/reedownlee/nor
160             self.NEWFILE_CONTENTS = "newfile contents\n"
161
162             return foo.get_metadata_for(u"bar.txt")
163         d.addCallback(_then)
164         def _got_metadata(metadata):
165             self._bar_txt_metadata = metadata
166         d.addCallback(_got_metadata)
167         return d
168
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()
173
174     def tearDown(self):
175         return self.s.stopService()
176
177     def failUnlessIsBarDotTxt(self, res):
178         self.failUnlessEqual(res, self.BAR_CONTENTS, res)
179
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))
188
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)
197
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"],
213                              self._bar_txt_uri)
214
215     def GET(self, urlpath, followRedirect=False):
216         url = self.webish_url + urlpath
217         return client.getPage(url, method="GET", followRedirect=followRedirect)
218
219     def PUT(self, urlpath, data):
220         url = self.webish_url + urlpath
221         return client.getPage(url, method="PUT", postdata=data)
222
223     def DELETE(self, urlpath):
224         url = self.webish_url + urlpath
225         return client.getPage(url, method="DELETE")
226
227     def POST(self, urlpath, followRedirect=False, **fields):
228         url = self.webish_url + urlpath
229         sepbase = "boogabooga"
230         sep = "--" + sepbase
231         form = []
232         form.append(sep)
233         form.append('Content-Disposition: form-data; name="_charset"')
234         form.append('')
235         form.append('UTF-8')
236         form.append(sep)
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")))
242             else:
243                 form.append('Content-Disposition: form-data; name="%s"' % name)
244             form.append('')
245             form.append(str(value))
246             form.append(sep)
247         form[-1] += "--"
248         body = "\r\n".join(form) + "\r\n"
249         headers = {"content-type": "multipart/form-data; boundary=%s" % sepbase,
250                    }
251         return client.getPage(url, method="POST", postdata=body,
252                               headers=headers, followRedirect=followRedirect)
253
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)
258             if substring:
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))
266         else:
267             self.fail("%s was supposed to raise %s, not get '%s'" %
268                       (which, expected_failure, res))
269
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)
274         def done(res):
275             if isinstance(res, failure.Failure):
276                 res.trap(expected_failure)
277                 if substring:
278                     self.failUnless(substring in str(res),
279                                     "substring '%s' not in '%s'"
280                                     % (substring, str(res)))
281             else:
282                 self.fail("%s was supposed to raise %s, not get '%s'" %
283                           (which, expected_failure, res))
284         d.addBoth(done)
285         return d
286
287     def should404(self, res, which):
288         if isinstance(res, failure.Failure):
289             res.trap(error.Error)
290             self.failUnlessEqual(res.value.status, "404")
291         else:
292             self.fail("%s was supposed to Error(404), not get '%s'" %
293                       (which, res))
294
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)
299             if code is not None:
300                 self.failUnlessEqual(res.value.status, str(code))
301             if substring:
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))
309         else:
310             self.fail("%s was supposed to Error(%s), not get '%s'" %
311                       (which, code, res))
312
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)
317         assert callable
318         d = defer.maybeDeferred(callable, *args, **kwargs)
319         d.addBoth(self.shouldHTTPError, which,
320                   code, substring, response_substring)
321         return d
322
323
324 class Web(WebMixin, unittest.TestCase):
325     def test_create(self):
326         pass
327
328     def test_welcome(self):
329         d = self.GET("/")
330         def _check(res):
331             self.failUnless('Welcome To AllMyData' in res)
332             self.failUnless('Tahoe' in res)
333
334             self.s.basedir = 'web/test_welcome'
335             fileutil.make_dirs("web/test_welcome")
336             fileutil.make_dirs("web/test_welcome/private")
337             return self.GET("/")
338         d.addCallback(_check)
339         return d
340
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)
347
348     def test_provisioning(self):
349         d = self.GET("/provisioning/")
350         def _check(res):
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",
358                       "num_servers": 30,
359                       "ownership_mode": "A",
360                       "download_rate": 100,
361                       "upload_rate": 10,
362                       "delete_rate": 10,
363                       "lease_timer": 7,
364                       }
365             return self.POST("/provisioning/", **fields)
366
367         d.addCallback(_check)
368         def _check2(res):
369             self.failUnless('Tahoe Provisioning Tool' in res)
370             self.failUnless("Share space consumed: 167.01TB" in res)
371
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",
381                       "drive_size": 1000,
382                       "download_rate": 1000,
383                       "upload_rate": 100,
384                       "delete_rate": 100,
385                       "lease_timer": 7,
386                       }
387             return self.POST("/provisioning/", **fields)
388         d.addCallback(_check2)
389         def _check3(res):
390             self.failUnless("Share space consumed: huge!" in res)
391             fields = {'filled': True}
392             return self.POST("/provisioning/", **fields)
393         d.addCallback(_check3)
394         def _check4(res):
395             self.failUnless("Share space consumed:" in res)
396         d.addCallback(_check4)
397         return d
398
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)
403         def _check(res):
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))
409         def _check_dl(res):
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))
413         def _check_ul(res):
414             self.failUnless("File Upload Status" in res, res)
415         d.addCallback(_check_ul)
416         return d
417
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")
429
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")
440
441     def test_GET_FILEURL(self):
442         d = self.GET(self.public_url + "/foo/bar.txt")
443         d.addCallback(self.failUnlessIsBarDotTxt)
444         return d
445
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
449         # header.
450         d.addCallback(self.failUnlessIsBarDotTxt)
451         return d
452
453     def test_GET_FILEURL_download(self):
454         d = self.GET(self.public_url + "/foo/bar.txt?t=download")
455         d.addCallback(self.failUnlessIsBarDotTxt)
456         return d
457
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")
461         return d
462
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))
471         return d
472
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))
481         return d
482
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",
487                   "409 Conflict",
488                   "There was already a child by that name, and you asked me "
489                   "to not replace it")
490         return d
491
492     def test_PUT_NEWFILEURL_mkdirs(self):
493         d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
494         fn = self._foo_node
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))
501         return d
502
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",
507                   "400 Bad Request",
508                   "cannot create directory because there is a file in the way")
509         return d
510
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"))
515         return d
516
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")
520         return d
521
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")
525         return d
526
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
531         # completely.
532         d = self.GET(self.public_url + "/foo/bar.txt?t=json")
533         d.addCallback(self.failUnlessIsBarJSON)
534         return d
535
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")
539         return d
540
541     def disable_local_access(self, res=None):
542         self.ws.allow_local_access(False)
543         return res
544
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")
550         d = self.GET(url)
551         def _done(res):
552             self.failUnless(os.path.exists(localfile))
553             data = open(localfile, "rb").read()
554             self.failUnlessEqual(data, self.BAR_CONTENTS)
555         d.addCallback(_done)
556         return d
557
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()
564         d = self.GET(url)
565         d.addBoth(self.shouldFail, error.Error, "localfile disabled",
566                   "403 Forbidden",
567                   "local file access is disabled")
568         return d
569
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",
581                   "403 Forbidden",
582                   "localfile= or localdir= requires a local connection")
583         def _check(res):
584             self.failIf(os.path.exists(localfile))
585         d.addCallback(_check)
586         def _reset(res):
587             webish.LOCALHOST = old_LOCALHOST
588             return res
589         d.addBoth(_reset)
590         return d
591
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",
598                   "403 Forbidden",
599                   "localfile= or localdir= requires an absolute path")
600         def _check(res):
601             self.failIf(os.path.exists(localfile))
602         d.addCallback(_check)
603         return d
604
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)
612         f.close()
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))
618         return d
619
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",
623                                   400, "Bad Request",
624                                   "t=upload requires localfile= or localdir=",
625                                   self.PUT, url, "")
626         return d
627
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)
635         f.close()
636         self.disable_local_access()
637         d = self.PUT(url, "")
638         d.addBoth(self.shouldFail, error.Error, "put localfile disabled",
639                   "403 Forbidden",
640                   "local file access is disabled")
641         return d
642
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)
648         f.close()
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,
659                                                       u"newdir/new.txt",
660                                                       self.NEWFILE_CONTENTS))
661         return d
662
663     def test_GET_FILEURL_uri(self):
664         d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
665         def _check(res):
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"))
670         def _check2(res):
671             # for now, for files, uris and readonly-uris are the same
672             self.failUnlessEqual(res, self._bar_txt_uri)
673         d.addCallback(_check2)
674         return d
675
676     def test_GET_FILEURL_badtype(self):
677         d = self.shouldHTTPError2("GET t=bogus", 400, "Bad Request",
678                                   "bad t=bogus",
679                                   self.GET,
680                                   self.public_url + "/foo/bar.txt?t=bogus")
681         return d
682
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")
686         return d
687
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)
691         def _check(res):
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>'
695                                       '</td>'
696                                       '\s+<td>FILE</td>'
697                                       '\s+<td>%d</td>' % len(self.BAR_CONTENTS)
698                                       , res))
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)
703
704         # look at a directory which is readonly
705         d.addCallback(lambda res:
706                       self.GET(self.public_url + "/reedownlee", followRedirect=True))
707         def _check2(res):
708             self.failUnless("(readonly)" in res, res)
709             self.failIf("Upload a file" in res, res)
710         d.addCallback(_check2)
711
712         # and at a directory that contains a readonly directory
713         d.addCallback(lambda res:
714                       self.GET(self.public_url, followRedirect=True))
715         def _check3(res):
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)
719
720         return d
721
722     def test_GET_DIRURL_badtype(self):
723         d = self.shouldHTTPError2("test_GET_DIRURL_badtype",
724                                   400, "Bad Request",
725                                   "bad t=bogus",
726                                   self.GET,
727                                   self.public_url + "/foo?t=bogus")
728         return d
729
730     def test_GET_DIRURL_json(self):
731         d = self.GET(self.public_url + "/foo?t=json")
732         d.addCallback(self.failUnlessIsFooJSON)
733         return d
734
735     def test_GET_DIRURL_manifest(self):
736         d = self.GET(self.public_url + "/foo?t=manifest", followRedirect=True)
737         def _got(manifest):
738             self.failUnless("Refresh Capabilities" in manifest)
739         d.addCallback(_got)
740         return d
741
742     def test_GET_DIRURL_uri(self):
743         d = self.GET(self.public_url + "/foo?t=uri")
744         def _check(res):
745             self.failUnlessEqual(res, self._foo_uri)
746         d.addCallback(_check)
747         return d
748
749     def test_GET_DIRURL_readonly_uri(self):
750         d = self.GET(self.public_url + "/foo?t=readonly-uri")
751         def _check(res):
752             self.failUnlessEqual(res, self._foo_readonly_uri)
753         d.addCallback(_check)
754         return d
755
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, [])
762         return d
763
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, [])
770         return d
771
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",
775                   "409 Conflict",
776                   "There was already a child by that name, and you asked me "
777                   "to not replace it")
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"])
782         return d
783
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, [])
793         return d
794
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"))
799         return d
800
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"))
806         return d
807
808     def test_DELETE_DIRURL_missing2(self):
809         d = self.DELETE(self.public_url + "/missing")
810         d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
811         return d
812
813     def test_walker(self):
814         out = []
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)
820         def _check(res):
821             names = [path for (path,node) in out]
822             self.failUnlessEqual(sorted(names),
823                                  [(u'foo',),
824                                   (u'foo',u'bar.txt'),
825                                   (u'foo',u'blockingfile'),
826                                   (u'foo', u'empty'),
827                                   (u'foo', u"n\u00fc.txt"),
828                                   (u'foo', u'sub'),
829                                   (u'foo',u'sub',u'baz.txt'),
830                                   (u'reedownlee',),
831                                   (u'reedownlee', u'nor'),
832                                   ])
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',
838                                 u'baz.txt', u'nor'):
839                     self.failUnless(interfaces.IFileNode.providedBy(node))
840                 else:
841                     self.failUnless(interfaces.IDirectoryNode.providedBy(node))
842         d.addCallback(_check)
843         return d
844
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))
850         def _check(res):
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)
860         return d
861
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",
869                   "403 Forbidden",
870                   "local file access is disabled")
871         return d
872
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",
879                   "403 Forbidden",
880                   "localfile= or localdir= requires an absolute path")
881         def _check(res):
882             self.failIf(os.path.exists(localdir))
883         d.addCallback(_check)
884         return d
885
886     def test_GET_DIRURL_localdir_nolocaldir(self):
887         d = self.shouldHTTPError2("GET_DIRURL_localdir_nolocaldir",
888                                   400, "Bad Request",
889                                   "t=download requires localdir=",
890                                   self.GET,
891                                   self.public_url + "/foo?t=download")
892         return d
893
894     def touch(self, localdir, filename):
895         path = os.path.join(localdir, filename)
896         f = open(path, "wb")
897         f.write("contents of %s\n" % filename)
898         f.close()
899
900     def dump_root(self):
901         print "NODEWALK"
902         w = webish.DirnodeWalkerMixin()
903         def visitor(childpath, childnode, metadata):
904             print childpath
905         d = w.walk(self.public_root, visitor)
906         return d
907
908     def failUnlessNodeKeysAre(self, node, expected_keys):
909         for k in expected_keys:
910             assert isinstance(k, unicode)
911         d = node.list()
912         def _check(children):
913             self.failUnlessEqual(sorted(children.keys()), sorted(expected_keys))
914         d.addCallback(_check)
915         return d
916     def failUnlessNodeHasChild(self, node, name):
917         assert isinstance(name, unicode)
918         d = node.list()
919         def _check(children):
920             self.failUnless(name in children)
921         d.addCallback(_check)
922         return d
923     def failIfNodeHasChild(self, node, name):
924         assert isinstance(name, unicode)
925         d = node.list()
926         def _check(children):
927             self.failIf(name in children)
928         d.addCallback(_check)
929         return d
930
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)
938         return d
939
940     def failUnlessChildURIIs(self, node, name, expected_uri):
941         assert isinstance(name, unicode)
942         d = node.get_child_at_path(name)
943         def _check(child):
944             self.failUnlessEqual(child.get_uri(), expected_uri.strip())
945         d.addCallback(_check)
946         return d
947
948     def failUnlessURIMatchesChild(self, got_uri, node, name):
949         assert isinstance(name, unicode)
950         d = node.get_child_at_path(name)
951         def _check(child):
952             self.failUnlessEqual(got_uri.strip(), child.get_uri())
953         d.addCallback(_check)
954         return d
955
956     def failUnlessCHKURIHasContents(self, got_uri, contents):
957         self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
958
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")
969
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"))
986         return d
987
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")
998
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",
1003                   "403 Forbidden",
1004                   "local file access is disabled")
1005         return d
1006
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")
1017
1018         d = self.PUT(self.public_url + "/foo/subdir/newdir?t=upload&localdir=%s"
1019                      % urllib.quote(localdir),
1020                      "")
1021         fn = self._foo_node
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"))
1036         return d
1037
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",
1044                                   400, "Bad Request",
1045                                   "%s doesn't exist!" % localdir,
1046                                   self.PUT, url, "")
1047         return d
1048
1049     def test_POST_upload(self):
1050         d = self.POST(self.public_url + "/foo", t="upload",
1051                       file=("new.txt", self.NEWFILE_CONTENTS))
1052         fn = self._foo_node
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))
1057         return d
1058
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))
1064         fn = self._foo_node
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,
1072                                                             contents))
1073         return d
1074
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)
1087             return new_uri
1088         d.addCallback(_check_upload_results)
1089         d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1090         return d
1091
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, "/")
1096         return d
1097
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()
1103             u = IURI(new_uri)
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)
1109         def _check2(data):
1110             self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1111         d.addCallback(_check2)
1112         return d
1113
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))
1118         fn = self._foo_node
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"))
1124         def _got(newnode):
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()
1129         d.addCallback(_got)
1130
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",
1135                                 mutable="true",
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",
1140                                                       NEWER_CONTENTS))
1141         d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1142         def _got2(newnode):
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)
1148
1149         # finally list the directory, since mutable files are displayed
1150         # differently
1151
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)
1159             return res
1160         d.addCallback(_check_page)
1161
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)
1167             self.failUnless(mo)
1168             formaction=mo.group(1)
1169             formname=mo.group(2)
1170             formwhendone=mo.group(3)
1171
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"))
1181         def _got3(newnode):
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)
1187
1188         return d
1189
1190     def test_POST_upload_replace(self):
1191         d = self.POST(self.public_url + "/foo", t="upload",
1192                       file=("bar.txt", self.NEWFILE_CONTENTS))
1193         fn = self._foo_node
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))
1198         return d
1199
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))
1206         return d
1207
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",
1213                   "409 Conflict",
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)
1218         return d
1219
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",
1224                   "409 Conflict",
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)
1229         return d
1230
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")
1235         fn = self._foo_node
1236         d.addCallback(lambda res:
1237                       self.failUnlessChildContentsAre(fn, u"new.txt",
1238                                                       self.NEWFILE_CONTENTS))
1239         return d
1240
1241     def test_POST_upload_named(self):
1242         fn = self._foo_node
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))
1249         return d
1250
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",
1256                   "400 Bad Request",
1257                   "name= may not contain a slash",
1258                   )
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",
1264                                                   u"sub"]))
1265         return d
1266
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, [])
1271         return d
1272
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)
1278         return d
1279
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)
1287         return d
1288
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")
1294         return d
1295
1296     def test_welcome_page_mkdir_button(self):
1297         # Fetch the welcome page.
1298         d = self.GET("/")
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)
1303             formt = mo.group(2)
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')
1313         return d
1314
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, [])
1319         return d
1320
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",
1325                   "409 Conflict",
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"])
1330         return d
1331
1332     def test_POST_mkdir_no_replace_field(self): # return value?
1333         d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
1334                       replace="false")
1335         d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
1336                   "409 Conflict",
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"])
1341         return d
1342
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, [])
1349         return d
1350
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, [])
1357         return d
1358
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)
1363
1364         reqbody = """{
1365                      "atomic_added_1": [ "filenode", { "rw_uri": "%s",
1366                                                 "size": 0,
1367                                                 "metadata": {
1368                                                   "ctime": 1002777696.7564139,
1369                                                   "mtime": 1002777696.7564139
1370                                                  }
1371                                                } ],
1372                      "atomic_added_2": [ "filenode", { "rw_uri": "%s",
1373                                                 "size": 1,
1374                                                 "metadata": {
1375                                                   "ctime": 1002777696.7564139,
1376                                                   "mtime": 1002777696.7564139
1377                                                  }
1378                                                } ],
1379                      "atomic_added_3": [ "filenode", { "rw_uri": "%s",
1380                                                 "size": 2,
1381                                                 "metadata": {
1382                                                   "ctime": 1002777696.7564139,
1383                                                   "mtime": 1002777696.7564139
1384                                                  }
1385                                                } ]
1386                     }""" % (newuri9, newuri10, newuri11)
1387
1388         url = self.webish_url + self.public_url + "/foo" + "?t=set_children"
1389
1390         d = client.getPage(url, method="POST", postdata=reqbody)
1391         def _then(res):
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")
1395
1396         d.addCallback(_then)
1397         return d
1398
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",
1405                                                       contents))
1406         return d
1407
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",
1414                                                       contents))
1415         return d
1416
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",
1423                   "409 Conflict",
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)
1428         return d
1429
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",
1436                   "409 Conflict",
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)
1441         return d
1442
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)
1449         return d
1450
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)
1462         return d
1463
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)
1476         return d
1477
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",
1484                   "409 Conflict",
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)
1489         return d
1490
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",
1497                   "409 Conflict",
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)
1502         return d
1503
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)
1508
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",
1514                   "400 Bad Request",
1515                   "to_name= may not contain a slash",
1516                   )
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",
1523                   "400 Bad Request",
1524                   "from_name= may not contain a slash",
1525                   )
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)
1534         return d
1535
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)
1545         return d
1546
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
1563
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)
1568         d = self.GET(base)
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")
1582
1583         return d
1584
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
1588         def _check(res):
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)
1592         return d
1593
1594     def log(self, res, msg):
1595         #print "MSG: %s  RES: %s" % (msg, res)
1596         log.msg(msg)
1597         return res
1598
1599     def test_GET_URI_URL(self):
1600         base = "/uri/%s" % self._bar_txt_uri
1601         d = self.GET(base)
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)
1607         return d
1608
1609     def test_GET_URI_URL_dir(self):
1610         base = "/uri/%s?t=json" % self._foo_uri
1611         d = self.GET(base)
1612         d.addCallback(self.failUnlessIsFooJSON)
1613         return d
1614
1615     def test_GET_URI_URL_missing(self):
1616         base = "/uri/%s" % self._bad_file_uri
1617         d = self.GET(base)
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.
1624         return d
1625
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",
1632                                                       contents))
1633         return d
1634
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",
1641                                                       contents))
1642         return d
1643
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",
1648                   "409 Conflict",
1649                   "There was already a child by that name, and you asked me "
1650                   "to not replace it")
1651         return d
1652
1653     def test_PUT_NEWFILE_URI(self):
1654         file_contents = "New file contents here\n"
1655         d = self.PUT("/uri", file_contents)
1656         def _check(uri):
1657             self.failUnless(uri in FakeCHKFileNode.all_contents)
1658             self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
1659                                  file_contents)
1660             return self.GET("/uri/%s" % uri)
1661         d.addCallback(_check)
1662         def _check2(res):
1663             self.failUnlessEqual(res, file_contents)
1664         d.addCallback(_check2)
1665         return d
1666
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",
1671                   "400 Bad Request",
1672                   "/uri only accepts PUT and PUT?t=mkdir")
1673         return d
1674
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):
1679             uri = uri.strip()
1680             u = IURI(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)
1689         return d
1690
1691         def _check(uri):
1692             self.failUnless(uri in FakeCHKFileNode.all_contents)
1693             self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
1694                                  file_contents)
1695             return self.GET("/uri/%s" % uri)
1696         d.addCallback(_check)
1697         def _check2(res):
1698             self.failUnlessEqual(res, file_contents)
1699         d.addCallback(_check2)
1700         return d
1701
1702     def test_PUT_mkdir(self):
1703         d = self.PUT("/uri?t=mkdir", "")
1704         def _check(uri):
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))
1709             return d2
1710         d.addCallback(_check)
1711         d.addCallback(self.failUnlessIsEmptyJSON)
1712         return d
1713
1714     def test_POST_check(self):
1715         d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
1716         def _done(res):
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
1721             # called.
1722             pass
1723         d.addCallback(_done)
1724         return d
1725
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")
1730         return d
1731
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")
1736         return d