]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/test/test_web.py
mutable WIP: add servermap update status pages
[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     convergence = "some random string"
39
40     def connected_to_introducer(self):
41         return False
42
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)
52
53     def create_empty_dirnode(self):
54         n = FakeDirectoryNode(self)
55         d = n.create()
56         d.addCallback(lambda res: n)
57         return d
58
59     def create_mutable_file(self, contents=""):
60         n = FakeMutableFileNode(self)
61         return n.create(contents)
62
63     def upload(self, uploadable):
64         d = uploadable.get_size()
65         d.addCallback(lambda size: uploadable.read(size))
66         def _got_data(datav):
67             data = "".join(datav)
68             n = create_chk_filenode(self, data)
69             results = upload.UploadResults()
70             results.uri = n.get_uri()
71             return results
72         d.addCallback(_got_data)
73         return d
74
75     def list_all_uploads(self):
76         return []
77     def list_all_downloads(self):
78         return []
79
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):
85         return []
86     def list_active_retrieve(self):
87         return []
88     def list_active_mapupdate(self):
89         return []
90     def list_recent_mapupdate(self):
91         return []
92
93     def list_recent_uploads(self):
94         return self._all_upload_status
95     def list_recent_downloads(self):
96         return self._all_download_status
97     def list_recent_publish(self):
98         return []
99     def list_recent_retrieve(self):
100         return []
101     def list_active_helper_statuses(self):
102         return []
103     def list_recent_helper_statuses(self):
104         return []
105
106 class WebMixin(object):
107     def setUp(self):
108         self.s = FakeClient()
109         self.s.startService()
110         self.ws = s = webish.WebishServer("0")
111         s.allow_local_access(True)
112         s.setServiceParent(self.s)
113         port = s.listener._port.getHost().port
114         self.webish_url = "http://localhost:%d" % port
115
116         l = [ self.s.create_empty_dirnode() for x in range(6) ]
117         d = defer.DeferredList(l)
118         def _then(res):
119             self.public_root = res[0][1]
120             assert interfaces.IDirectoryNode.providedBy(self.public_root), res
121             self.public_url = "/uri/" + self.public_root.get_uri()
122             self.private_root = res[1][1]
123
124             foo = res[2][1]
125             self._foo_node = foo
126             self._foo_uri = foo.get_uri()
127             self._foo_readonly_uri = foo.get_readonly_uri()
128             # NOTE: we ignore the deferred on all set_uri() calls, because we
129             # know the fake nodes do these synchronously
130             self.public_root.set_uri(u"foo", foo.get_uri())
131
132             self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
133             foo.set_uri(u"bar.txt", self._bar_txt_uri)
134
135             foo.set_uri(u"empty", res[3][1].get_uri())
136             sub_uri = res[4][1].get_uri()
137             foo.set_uri(u"sub", sub_uri)
138             sub = self.s.create_node_from_uri(sub_uri)
139
140             _ign, n, blocking_uri = self.makefile(1)
141             foo.set_uri(u"blockingfile", blocking_uri)
142
143             unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
144             # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
145             # still think of it as an umlaut
146             foo.set_uri(unicode_filename, self._bar_txt_uri)
147
148             _ign, n, baz_file = self.makefile(2)
149             sub.set_uri(u"baz.txt", baz_file)
150
151             _ign, n, self._bad_file_uri = self.makefile(3)
152             # this uri should not be downloadable
153             del FakeCHKFileNode.all_contents[self._bad_file_uri]
154
155             rodir = res[5][1]
156             self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri())
157             rodir.set_uri(u"nor", baz_file)
158
159             # public/
160             # public/foo/
161             # public/foo/bar.txt
162             # public/foo/blockingfile
163             # public/foo/empty/
164             # public/foo/sub/
165             # public/foo/sub/baz.txt
166             # public/reedownlee/
167             # public/reedownlee/nor
168             self.NEWFILE_CONTENTS = "newfile contents\n"
169
170             return foo.get_metadata_for(u"bar.txt")
171         d.addCallback(_then)
172         def _got_metadata(metadata):
173             self._bar_txt_metadata = metadata
174         d.addCallback(_got_metadata)
175         return d
176
177     def makefile(self, number):
178         contents = "contents of file %s\n" % number
179         n = create_chk_filenode(self.s, contents)
180         return contents, n, n.get_uri()
181
182     def tearDown(self):
183         return self.s.stopService()
184
185     def failUnlessIsBarDotTxt(self, res):
186         self.failUnlessEqual(res, self.BAR_CONTENTS, res)
187
188     def failUnlessIsBarJSON(self, res):
189         data = simplejson.loads(res)
190         self.failUnless(isinstance(data, list))
191         self.failUnlessEqual(data[0], "filenode")
192         self.failUnless(isinstance(data[1], dict))
193         self.failIf("rw_uri" in data[1]) # immutable
194         self.failUnlessEqual(data[1]["ro_uri"], self._bar_txt_uri)
195         self.failUnlessEqual(data[1]["size"], len(self.BAR_CONTENTS))
196
197     def failUnlessIsFooJSON(self, res):
198         data = simplejson.loads(res)
199         self.failUnless(isinstance(data, list))
200         self.failUnlessEqual(data[0], "dirnode", res)
201         self.failUnless(isinstance(data[1], dict))
202         self.failUnless("rw_uri" in data[1]) # mutable
203         self.failUnlessEqual(data[1]["rw_uri"], self._foo_uri)
204         self.failUnlessEqual(data[1]["ro_uri"], self._foo_readonly_uri)
205
206         kidnames = sorted(data[1]["children"])
207         self.failUnlessEqual(kidnames,
208                              [u"bar.txt", u"blockingfile", u"empty",
209                               u"n\u00fc.txt", u"sub"])
210         kids = data[1]["children"]
211         self.failUnlessEqual(kids[u"sub"][0], "dirnode")
212         self.failUnless("metadata" in kids[u"sub"][1])
213         self.failUnless("ctime" in kids[u"sub"][1]["metadata"])
214         self.failUnless("mtime" in kids[u"sub"][1]["metadata"])
215         self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
216         self.failUnlessEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
217         self.failUnlessEqual(kids[u"bar.txt"][1]["ro_uri"], self._bar_txt_uri)
218         self.failUnlessEqual(kids[u"bar.txt"][1]["metadata"]["ctime"],
219                              self._bar_txt_metadata["ctime"])
220         self.failUnlessEqual(kids[u"n\u00fc.txt"][1]["ro_uri"],
221                              self._bar_txt_uri)
222
223     def GET(self, urlpath, followRedirect=False):
224         url = self.webish_url + urlpath
225         return client.getPage(url, method="GET", followRedirect=followRedirect)
226
227     def PUT(self, urlpath, data):
228         url = self.webish_url + urlpath
229         return client.getPage(url, method="PUT", postdata=data)
230
231     def DELETE(self, urlpath):
232         url = self.webish_url + urlpath
233         return client.getPage(url, method="DELETE")
234
235     def POST(self, urlpath, followRedirect=False, **fields):
236         url = self.webish_url + urlpath
237         sepbase = "boogabooga"
238         sep = "--" + sepbase
239         form = []
240         form.append(sep)
241         form.append('Content-Disposition: form-data; name="_charset"')
242         form.append('')
243         form.append('UTF-8')
244         form.append(sep)
245         for name, value in fields.iteritems():
246             if isinstance(value, tuple):
247                 filename, value = value
248                 form.append('Content-Disposition: form-data; name="%s"; '
249                             'filename="%s"' % (name, filename.encode("utf-8")))
250             else:
251                 form.append('Content-Disposition: form-data; name="%s"' % name)
252             form.append('')
253             form.append(str(value))
254             form.append(sep)
255         form[-1] += "--"
256         body = "\r\n".join(form) + "\r\n"
257         headers = {"content-type": "multipart/form-data; boundary=%s" % sepbase,
258                    }
259         return client.getPage(url, method="POST", postdata=body,
260                               headers=headers, followRedirect=followRedirect)
261
262     def shouldFail(self, res, expected_failure, which,
263                    substring=None, response_substring=None):
264         if isinstance(res, failure.Failure):
265             res.trap(expected_failure)
266             if substring:
267                 self.failUnless(substring in str(res),
268                                 "substring '%s' not in '%s'"
269                                 % (substring, str(res)))
270             if response_substring:
271                 self.failUnless(response_substring in res.value.response,
272                                 "respose substring '%s' not in '%s'"
273                                 % (response_substring, res.value.response))
274         else:
275             self.fail("%s was supposed to raise %s, not get '%s'" %
276                       (which, expected_failure, res))
277
278     def shouldFail2(self, expected_failure, which, substring,
279                     response_substring,
280                     callable, *args, **kwargs):
281         assert substring is None or isinstance(substring, str)
282         assert response_substring is None or isinstance(response_substring, str)
283         d = defer.maybeDeferred(callable, *args, **kwargs)
284         def done(res):
285             if isinstance(res, failure.Failure):
286                 res.trap(expected_failure)
287                 if substring:
288                     self.failUnless(substring in str(res),
289                                     "substring '%s' not in '%s'"
290                                     % (substring, str(res)))
291                 if response_substring:
292                     self.failUnless(response_substring in res.value.response,
293                                     "respose substring '%s' not in '%s'"
294                                     % (response_substring, res.value.response))
295             else:
296                 self.fail("%s was supposed to raise %s, not get '%s'" %
297                           (which, expected_failure, res))
298         d.addBoth(done)
299         return d
300
301     def should404(self, res, which):
302         if isinstance(res, failure.Failure):
303             res.trap(error.Error)
304             self.failUnlessEqual(res.value.status, "404")
305         else:
306             self.fail("%s was supposed to Error(404), not get '%s'" %
307                       (which, res))
308
309     def shouldHTTPError(self, res, which, code=None, substring=None,
310                         response_substring=None):
311         if isinstance(res, failure.Failure):
312             res.trap(error.Error)
313             if code is not None:
314                 self.failUnlessEqual(res.value.status, str(code))
315             if substring:
316                 self.failUnless(substring in str(res),
317                                 "substring '%s' not in '%s'"
318                                 % (substring, str(res)))
319             if response_substring:
320                 self.failUnless(response_substring in res.value.response,
321                                 "respose substring '%s' not in '%s'"
322                                 % (response_substring, res.value.response))
323         else:
324             self.fail("%s was supposed to Error(%s), not get '%s'" %
325                       (which, code, res))
326
327     def shouldHTTPError2(self, which,
328                          code=None, substring=None, response_substring=None,
329                          callable=None, *args, **kwargs):
330         assert substring is None or isinstance(substring, str)
331         assert callable
332         d = defer.maybeDeferred(callable, *args, **kwargs)
333         d.addBoth(self.shouldHTTPError, which,
334                   code, substring, response_substring)
335         return d
336
337
338 class Web(WebMixin, unittest.TestCase):
339     def test_create(self):
340         pass
341
342     def test_welcome(self):
343         d = self.GET("/")
344         def _check(res):
345             self.failUnless('Welcome To AllMyData' in res)
346             self.failUnless('Tahoe' in res)
347
348             self.s.basedir = 'web/test_welcome'
349             fileutil.make_dirs("web/test_welcome")
350             fileutil.make_dirs("web/test_welcome/private")
351             return self.GET("/")
352         d.addCallback(_check)
353         return d
354
355     def test_provisioning_math(self):
356         self.failUnlessEqual(provisioning.binomial(10, 0), 1)
357         self.failUnlessEqual(provisioning.binomial(10, 1), 10)
358         self.failUnlessEqual(provisioning.binomial(10, 2), 45)
359         self.failUnlessEqual(provisioning.binomial(10, 9), 10)
360         self.failUnlessEqual(provisioning.binomial(10, 10), 1)
361
362     def test_provisioning(self):
363         d = self.GET("/provisioning/")
364         def _check(res):
365             self.failUnless('Tahoe Provisioning Tool' in res)
366             fields = {'filled': True,
367                       "num_users": int(50e3),
368                       "files_per_user": 1000,
369                       "space_per_user": int(1e9),
370                       "sharing_ratio": 1.0,
371                       "encoding_parameters": "3-of-10-5",
372                       "num_servers": 30,
373                       "ownership_mode": "A",
374                       "download_rate": 100,
375                       "upload_rate": 10,
376                       "delete_rate": 10,
377                       "lease_timer": 7,
378                       }
379             return self.POST("/provisioning/", **fields)
380
381         d.addCallback(_check)
382         def _check2(res):
383             self.failUnless('Tahoe Provisioning Tool' in res)
384             self.failUnless("Share space consumed: 167.01TB" in res)
385
386             fields = {'filled': True,
387                       "num_users": int(50e6),
388                       "files_per_user": 1000,
389                       "space_per_user": int(5e9),
390                       "sharing_ratio": 1.0,
391                       "encoding_parameters": "25-of-100-50",
392                       "num_servers": 30000,
393                       "ownership_mode": "E",
394                       "drive_failure_model": "U",
395                       "drive_size": 1000,
396                       "download_rate": 1000,
397                       "upload_rate": 100,
398                       "delete_rate": 100,
399                       "lease_timer": 7,
400                       }
401             return self.POST("/provisioning/", **fields)
402         d.addCallback(_check2)
403         def _check3(res):
404             self.failUnless("Share space consumed: huge!" in res)
405             fields = {'filled': True}
406             return self.POST("/provisioning/", **fields)
407         d.addCallback(_check3)
408         def _check4(res):
409             self.failUnless("Share space consumed:" in res)
410         d.addCallback(_check4)
411         return d
412
413     def test_status(self):
414         dl_num = self.s.list_recent_downloads()[0].get_counter()
415         ul_num = self.s.list_recent_uploads()[0].get_counter()
416         d = self.GET("/status", followRedirect=True)
417         def _check(res):
418             self.failUnless('Upload and Download Status' in res, res)
419             self.failUnless('"down-%d"' % dl_num in res, res)
420             self.failUnless('"up-%d"' % ul_num in res, res)
421         d.addCallback(_check)
422         d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
423         def _check_dl(res):
424             self.failUnless("File Download Status" in res, res)
425         d.addCallback(_check_dl)
426         d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
427         def _check_ul(res):
428             self.failUnless("File Upload Status" in res, res)
429         d.addCallback(_check_ul)
430         return d
431
432     def test_status_numbers(self):
433         drrm = status.DownloadResultsRendererMixin()
434         self.failUnlessEqual(drrm.render_time(None, None), "")
435         self.failUnlessEqual(drrm.render_time(None, 2.5), "2.50s")
436         self.failUnlessEqual(drrm.render_time(None, 0.25), "250ms")
437         self.failUnlessEqual(drrm.render_time(None, 0.0021), "2.1ms")
438         self.failUnlessEqual(drrm.render_time(None, 0.000123), "123us")
439         self.failUnlessEqual(drrm.render_rate(None, None), "")
440         self.failUnlessEqual(drrm.render_rate(None, 2500000), "2.50MBps")
441         self.failUnlessEqual(drrm.render_rate(None, 30100), "30.1kBps")
442         self.failUnlessEqual(drrm.render_rate(None, 123), "123Bps")
443
444         urrm = status.UploadResultsRendererMixin()
445         self.failUnlessEqual(urrm.render_time(None, None), "")
446         self.failUnlessEqual(urrm.render_time(None, 2.5), "2.50s")
447         self.failUnlessEqual(urrm.render_time(None, 0.25), "250ms")
448         self.failUnlessEqual(urrm.render_time(None, 0.0021), "2.1ms")
449         self.failUnlessEqual(urrm.render_time(None, 0.000123), "123us")
450         self.failUnlessEqual(urrm.render_rate(None, None), "")
451         self.failUnlessEqual(urrm.render_rate(None, 2500000), "2.50MBps")
452         self.failUnlessEqual(urrm.render_rate(None, 30100), "30.1kBps")
453         self.failUnlessEqual(urrm.render_rate(None, 123), "123Bps")
454
455     def test_GET_FILEURL(self):
456         d = self.GET(self.public_url + "/foo/bar.txt")
457         d.addCallback(self.failUnlessIsBarDotTxt)
458         return d
459
460     def test_GET_FILEURL_save(self):
461         d = self.GET(self.public_url + "/foo/bar.txt?save=bar.txt")
462         # TODO: look at the headers, expect a Content-Disposition: attachment
463         # header.
464         d.addCallback(self.failUnlessIsBarDotTxt)
465         return d
466
467     def test_GET_FILEURL_download(self):
468         d = self.GET(self.public_url + "/foo/bar.txt?t=download")
469         d.addCallback(self.failUnlessIsBarDotTxt)
470         return d
471
472     def test_GET_FILEURL_missing(self):
473         d = self.GET(self.public_url + "/foo/missing")
474         d.addBoth(self.should404, "test_GET_FILEURL_missing")
475         return d
476
477     def test_PUT_NEWFILEURL(self):
478         d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
479         # TODO: we lose the response code, so we can't check this
480         #self.failUnlessEqual(responsecode, 201)
481         d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
482         d.addCallback(lambda res:
483                       self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
484                                                       self.NEWFILE_CONTENTS))
485         return d
486
487     def test_PUT_NEWFILEURL_replace(self):
488         d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
489         # TODO: we lose the response code, so we can't check this
490         #self.failUnlessEqual(responsecode, 200)
491         d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
492         d.addCallback(lambda res:
493                       self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
494                                                       self.NEWFILE_CONTENTS))
495         return d
496
497     def test_PUT_NEWFILEURL_no_replace(self):
498         d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
499                      self.NEWFILE_CONTENTS)
500         d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
501                   "409 Conflict",
502                   "There was already a child by that name, and you asked me "
503                   "to not replace it")
504         return d
505
506     def test_PUT_NEWFILEURL_mkdirs(self):
507         d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
508         fn = self._foo_node
509         d.addCallback(self.failUnlessURIMatchesChild, fn, u"newdir/new.txt")
510         d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
511         d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
512         d.addCallback(lambda res:
513                       self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
514                                                       self.NEWFILE_CONTENTS))
515         return d
516
517     def test_PUT_NEWFILEURL_blocked(self):
518         d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
519                      self.NEWFILE_CONTENTS)
520         d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
521                   "400 Bad Request",
522                   "cannot create directory because there is a file in the way")
523         return d
524
525     def test_DELETE_FILEURL(self):
526         d = self.DELETE(self.public_url + "/foo/bar.txt")
527         d.addCallback(lambda res:
528                       self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
529         return d
530
531     def test_DELETE_FILEURL_missing(self):
532         d = self.DELETE(self.public_url + "/foo/missing")
533         d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
534         return d
535
536     def test_DELETE_FILEURL_missing2(self):
537         d = self.DELETE(self.public_url + "/missing/missing")
538         d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
539         return d
540
541     def test_GET_FILEURL_json(self):
542         # twisted.web.http.parse_qs ignores any query args without an '=', so
543         # I can't do "GET /path?json", I have to do "GET /path/t=json"
544         # instead. This may make it tricky to emulate the S3 interface
545         # completely.
546         d = self.GET(self.public_url + "/foo/bar.txt?t=json")
547         d.addCallback(self.failUnlessIsBarJSON)
548         return d
549
550     def test_GET_FILEURL_json_missing(self):
551         d = self.GET(self.public_url + "/foo/missing?json")
552         d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
553         return d
554
555     def disable_local_access(self, res=None):
556         self.ws.allow_local_access(False)
557         return res
558
559     def test_GET_FILEURL_localfile(self):
560         localfile = os.path.abspath("web/GET_FILEURL_local file")
561         url = (self.public_url + "/foo/bar.txt?t=download&localfile=%s" %
562                urllib.quote(localfile))
563         fileutil.make_dirs("web")
564         d = self.GET(url)
565         def _done(res):
566             self.failUnless(os.path.exists(localfile))
567             data = open(localfile, "rb").read()
568             self.failUnlessEqual(data, self.BAR_CONTENTS)
569         d.addCallback(_done)
570         return d
571
572     def test_GET_FILEURL_localfile_disabled(self):
573         localfile = os.path.abspath("web/GET_FILEURL_local file_disabled")
574         url = (self.public_url + "/foo/bar.txt?t=download&localfile=%s" %
575                urllib.quote(localfile))
576         fileutil.make_dirs("web")
577         self.disable_local_access()
578         d = self.GET(url)
579         d.addBoth(self.shouldFail, error.Error, "localfile disabled",
580                   "403 Forbidden",
581                   "local file access is disabled")
582         return d
583
584     def test_GET_FILEURL_localfile_nonlocal(self):
585         # TODO: somehow pretend that we aren't local, and verify that the
586         # server refuses to write to local files, probably by changing the
587         # server's idea of what counts as "local".
588         old_LOCALHOST = webish.LOCALHOST
589         webish.LOCALHOST = "127.0.0.2"
590         localfile = os.path.abspath("web/GET_FILEURL_local file_nonlocal")
591         fileutil.make_dirs("web")
592         d = self.GET(self.public_url + "/foo/bar.txt?t=download&localfile=%s"
593                      % urllib.quote(localfile))
594         d.addBoth(self.shouldFail, error.Error, "localfile non-local",
595                   "403 Forbidden",
596                   "localfile= or localdir= requires a local connection")
597         def _check(res):
598             self.failIf(os.path.exists(localfile))
599         d.addCallback(_check)
600         def _reset(res):
601             webish.LOCALHOST = old_LOCALHOST
602             return res
603         d.addBoth(_reset)
604         return d
605
606     def test_GET_FILEURL_localfile_nonabsolute(self):
607         localfile = "web/nonabsolute/path"
608         fileutil.make_dirs("web/nonabsolute")
609         d = self.GET(self.public_url + "/foo/bar.txt?t=download&localfile=%s"
610                      % urllib.quote(localfile))
611         d.addBoth(self.shouldFail, error.Error, "localfile non-absolute",
612                   "403 Forbidden",
613                   "localfile= or localdir= requires an absolute path")
614         def _check(res):
615             self.failIf(os.path.exists(localfile))
616         d.addCallback(_check)
617         return d
618
619     def test_PUT_NEWFILEURL_localfile(self):
620         localfile = os.path.abspath("web/PUT_NEWFILEURL_local file")
621         url = (self.public_url + "/foo/new.txt?t=upload&localfile=%s" %
622                urllib.quote(localfile))
623         fileutil.make_dirs("web")
624         f = open(localfile, "wb")
625         f.write(self.NEWFILE_CONTENTS)
626         f.close()
627         d = self.PUT(url, "")
628         d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
629         d.addCallback(lambda res:
630                       self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
631                                                       self.NEWFILE_CONTENTS))
632         return d
633
634     def test_PUT_NEWFILEURL_localfile_missingarg(self):
635         url = self.public_url + "/foo/new.txt?t=upload"
636         d = self.shouldHTTPError2("test_PUT_NEWFILEURL_localfile_missing",
637                                   400, "Bad Request",
638                                   "t=upload requires localfile= or localdir=",
639                                   self.PUT, url, "")
640         return d
641
642     def test_PUT_NEWFILEURL_localfile_disabled(self):
643         localfile = os.path.abspath("web/PUT_NEWFILEURL_local file_disabled")
644         url = (self.public_url + "/foo/new.txt?t=upload&localfile=%s" %
645                urllib.quote(localfile))
646         fileutil.make_dirs("web")
647         f = open(localfile, "wb")
648         f.write(self.NEWFILE_CONTENTS)
649         f.close()
650         self.disable_local_access()
651         d = self.PUT(url, "")
652         d.addBoth(self.shouldFail, error.Error, "put localfile disabled",
653                   "403 Forbidden",
654                   "local file access is disabled")
655         return d
656
657     def test_PUT_NEWFILEURL_localfile_mkdirs(self):
658         localfile = os.path.abspath("web/PUT_NEWFILEURL_local file_mkdirs")
659         fileutil.make_dirs("web")
660         f = open(localfile, "wb")
661         f.write(self.NEWFILE_CONTENTS)
662         f.close()
663         d = self.PUT(self.public_url + "/foo/newdir/new.txt?t=upload&localfile=%s"
664                      % urllib.quote(localfile), "")
665         d.addCallback(self.failUnlessURIMatchesChild,
666                       self._foo_node, u"newdir/new.txt")
667         d.addCallback(lambda res:
668                       self.failIfNodeHasChild(self._foo_node, u"new.txt"))
669         d.addCallback(lambda res:
670                       self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
671         d.addCallback(lambda res:
672                       self.failUnlessChildContentsAre(self._foo_node,
673                                                       u"newdir/new.txt",
674                                                       self.NEWFILE_CONTENTS))
675         return d
676
677     def test_GET_FILEURL_uri(self):
678         d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
679         def _check(res):
680             self.failUnlessEqual(res, self._bar_txt_uri)
681         d.addCallback(_check)
682         d.addCallback(lambda res:
683                       self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
684         def _check2(res):
685             # for now, for files, uris and readonly-uris are the same
686             self.failUnlessEqual(res, self._bar_txt_uri)
687         d.addCallback(_check2)
688         return d
689
690     def test_GET_FILEURL_badtype(self):
691         d = self.shouldHTTPError2("GET t=bogus", 400, "Bad Request",
692                                   "bad t=bogus",
693                                   self.GET,
694                                   self.public_url + "/foo/bar.txt?t=bogus")
695         return d
696
697     def test_GET_FILEURL_uri_missing(self):
698         d = self.GET(self.public_url + "/foo/missing?t=uri")
699         d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
700         return d
701
702     def test_GET_DIRURL(self):
703         # the addSlash means we get a redirect here
704         d = self.GET(self.public_url + "/foo", followRedirect=True)
705         def _check(res):
706             # the FILE reference points to a URI, but it should end in bar.txt
707             self.failUnless(re.search(r'<td>'
708                                       '<a href="[^"]+bar.txt">bar.txt</a>'
709                                       '</td>'
710                                       '\s+<td>FILE</td>'
711                                       '\s+<td>%d</td>' % len(self.BAR_CONTENTS)
712                                       , res))
713             # the DIR reference just points to a URI
714             self.failUnless(re.search(r'<td><a href="/uri/URI%3ADIR2%3A[^"]+">sub</a></td>'
715                                       '\s+<td>DIR</td>', res))
716         d.addCallback(_check)
717
718         # look at a directory which is readonly
719         d.addCallback(lambda res:
720                       self.GET(self.public_url + "/reedownlee", followRedirect=True))
721         def _check2(res):
722             self.failUnless("(readonly)" in res, res)
723             self.failIf("Upload a file" in res, res)
724         d.addCallback(_check2)
725
726         # and at a directory that contains a readonly directory
727         d.addCallback(lambda res:
728                       self.GET(self.public_url, followRedirect=True))
729         def _check3(res):
730             self.failUnless(re.search(r'<td><a href="/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a>'
731                                       '</td>\s+<td>DIR-RO</td>', res))
732         d.addCallback(_check3)
733
734         return d
735
736     def test_GET_DIRURL_badtype(self):
737         d = self.shouldHTTPError2("test_GET_DIRURL_badtype",
738                                   400, "Bad Request",
739                                   "bad t=bogus",
740                                   self.GET,
741                                   self.public_url + "/foo?t=bogus")
742         return d
743
744     def test_GET_DIRURL_json(self):
745         d = self.GET(self.public_url + "/foo?t=json")
746         d.addCallback(self.failUnlessIsFooJSON)
747         return d
748
749     def test_GET_DIRURL_manifest(self):
750         d = self.GET(self.public_url + "/foo?t=manifest", followRedirect=True)
751         def _got(manifest):
752             self.failUnless("Refresh Capabilities" in manifest)
753         d.addCallback(_got)
754         return d
755
756     def test_GET_DIRURL_deepsize(self):
757         d = self.GET(self.public_url + "/foo?t=deep-size", followRedirect=True)
758         def _got(manifest):
759             self.failUnless(re.search(r'^\d+$', manifest), manifest)
760             self.failUnlessEqual("57", manifest)
761         d.addCallback(_got)
762         return d
763
764     def test_GET_DIRURL_uri(self):
765         d = self.GET(self.public_url + "/foo?t=uri")
766         def _check(res):
767             self.failUnlessEqual(res, self._foo_uri)
768         d.addCallback(_check)
769         return d
770
771     def test_GET_DIRURL_readonly_uri(self):
772         d = self.GET(self.public_url + "/foo?t=readonly-uri")
773         def _check(res):
774             self.failUnlessEqual(res, self._foo_readonly_uri)
775         d.addCallback(_check)
776         return d
777
778     def test_PUT_NEWDIRURL(self):
779         d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
780         d.addCallback(lambda res:
781                       self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
782         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
783         d.addCallback(self.failUnlessNodeKeysAre, [])
784         return d
785
786     def test_PUT_NEWDIRURL_replace(self):
787         d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
788         d.addCallback(lambda res:
789                       self.failUnlessNodeHasChild(self._foo_node, u"sub"))
790         d.addCallback(lambda res: self._foo_node.get(u"sub"))
791         d.addCallback(self.failUnlessNodeKeysAre, [])
792         return d
793
794     def test_PUT_NEWDIRURL_no_replace(self):
795         d = self.PUT(self.public_url + "/foo/sub?t=mkdir&replace=false", "")
796         d.addBoth(self.shouldFail, error.Error, "PUT_NEWDIRURL_no_replace",
797                   "409 Conflict",
798                   "There was already a child by that name, and you asked me "
799                   "to not replace it")
800         d.addCallback(lambda res:
801                       self.failUnlessNodeHasChild(self._foo_node, u"sub"))
802         d.addCallback(lambda res: self._foo_node.get(u"sub"))
803         d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
804         return d
805
806     def test_PUT_NEWDIRURL_mkdir_p(self):
807         d = defer.succeed(None)
808         d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
809         d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
810         d.addCallback(lambda res: self._foo_node.get(u"mkp"))
811         def mkdir_p(mkpnode):
812             url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
813             d = self.POST(url)
814             def made_subsub(ssuri):
815                 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
816                 d.addCallback(lambda ssnode: self.failUnlessEqual(ssnode.get_uri(), ssuri))
817                 d = self.POST(url)
818                 d.addCallback(lambda uri2: self.failUnlessEqual(uri2, ssuri))
819                 return d
820             d.addCallback(made_subsub)
821             return d
822         d.addCallback(mkdir_p)
823         return d
824
825     def test_PUT_NEWDIRURL_mkdirs(self):
826         d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
827         d.addCallback(lambda res:
828                       self.failIfNodeHasChild(self._foo_node, u"newdir"))
829         d.addCallback(lambda res:
830                       self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
831         d.addCallback(lambda res:
832                       self._foo_node.get_child_at_path(u"subdir/newdir"))
833         d.addCallback(self.failUnlessNodeKeysAre, [])
834         return d
835
836     def test_DELETE_DIRURL(self):
837         d = self.DELETE(self.public_url + "/foo")
838         d.addCallback(lambda res:
839                       self.failIfNodeHasChild(self.public_root, u"foo"))
840         return d
841
842     def test_DELETE_DIRURL_missing(self):
843         d = self.DELETE(self.public_url + "/foo/missing")
844         d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
845         d.addCallback(lambda res:
846                       self.failUnlessNodeHasChild(self.public_root, u"foo"))
847         return d
848
849     def test_DELETE_DIRURL_missing2(self):
850         d = self.DELETE(self.public_url + "/missing")
851         d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
852         return d
853
854     def test_walker(self):
855         out = []
856         def _visitor(path, node, metadata):
857             out.append((path, node))
858             return defer.succeed(None)
859         w = webish.DirnodeWalkerMixin()
860         d = w.walk(self.public_root, _visitor)
861         def _check(res):
862             names = [path for (path,node) in out]
863             self.failUnlessEqual(sorted(names),
864                                  [(u'foo',),
865                                   (u'foo',u'bar.txt'),
866                                   (u'foo',u'blockingfile'),
867                                   (u'foo', u'empty'),
868                                   (u'foo', u"n\u00fc.txt"),
869                                   (u'foo', u'sub'),
870                                   (u'foo',u'sub',u'baz.txt'),
871                                   (u'reedownlee',),
872                                   (u'reedownlee', u'nor'),
873                                   ])
874             subindex = names.index( (u'foo', u'sub') )
875             bazindex = names.index( (u'foo', u'sub', u'baz.txt') )
876             self.failUnless(subindex < bazindex)
877             for path,node in out:
878                 if path[-1] in (u'bar.txt', u"n\u00fc.txt", u'blockingfile',
879                                 u'baz.txt', u'nor'):
880                     self.failUnless(interfaces.IFileNode.providedBy(node))
881                 else:
882                     self.failUnless(interfaces.IDirectoryNode.providedBy(node))
883         d.addCallback(_check)
884         return d
885
886     def test_GET_DIRURL_localdir(self):
887         localdir = os.path.abspath("web/GET_DIRURL_local dir")
888         fileutil.make_dirs("web")
889         d = self.GET(self.public_url + "/foo?t=download&localdir=%s" %
890                      urllib.quote(localdir))
891         def _check(res):
892             barfile = os.path.join(localdir, "bar.txt")
893             self.failUnless(os.path.exists(barfile))
894             data = open(barfile, "rb").read()
895             self.failUnlessEqual(data, self.BAR_CONTENTS)
896             blockingfile = os.path.join(localdir, "blockingfile")
897             self.failUnless(os.path.exists(blockingfile))
898             subdir = os.path.join(localdir, "sub")
899             self.failUnless(os.path.isdir(subdir))
900         d.addCallback(_check)
901         return d
902
903     def test_GET_DIRURL_localdir_disabled(self):
904         localdir = os.path.abspath("web/GET_DIRURL_local dir_disabled")
905         fileutil.make_dirs("web")
906         self.disable_local_access()
907         d = self.GET(self.public_url + "/foo?t=download&localdir=%s" %
908                      urllib.quote(localdir))
909         d.addBoth(self.shouldFail, error.Error, "localfile disabled",
910                   "403 Forbidden",
911                   "local file access is disabled")
912         return d
913
914     def test_GET_DIRURL_localdir_nonabsolute(self):
915         localdir = "web/nonabsolute/dir path"
916         fileutil.make_dirs("web/nonabsolute")
917         d = self.GET(self.public_url + "/foo?t=download&localdir=%s" %
918                      urllib.quote(localdir))
919         d.addBoth(self.shouldFail, error.Error, "localdir non-absolute",
920                   "403 Forbidden",
921                   "localfile= or localdir= requires an absolute path")
922         def _check(res):
923             self.failIf(os.path.exists(localdir))
924         d.addCallback(_check)
925         return d
926
927     def test_GET_DIRURL_localdir_nolocaldir(self):
928         d = self.shouldHTTPError2("GET_DIRURL_localdir_nolocaldir",
929                                   400, "Bad Request",
930                                   "t=download requires localdir=",
931                                   self.GET,
932                                   self.public_url + "/foo?t=download")
933         return d
934
935     def touch(self, localdir, filename):
936         path = os.path.join(localdir, filename)
937         f = open(path, "wb")
938         f.write("contents of %s\n" % filename)
939         f.close()
940
941     def dump_root(self):
942         print "NODEWALK"
943         w = webish.DirnodeWalkerMixin()
944         def visitor(childpath, childnode, metadata):
945             print childpath
946         d = w.walk(self.public_root, visitor)
947         return d
948
949     def failUnlessNodeKeysAre(self, node, expected_keys):
950         for k in expected_keys:
951             assert isinstance(k, unicode)
952         d = node.list()
953         def _check(children):
954             self.failUnlessEqual(sorted(children.keys()), sorted(expected_keys))
955         d.addCallback(_check)
956         return d
957     def failUnlessNodeHasChild(self, node, name):
958         assert isinstance(name, unicode)
959         d = node.list()
960         def _check(children):
961             self.failUnless(name in children)
962         d.addCallback(_check)
963         return d
964     def failIfNodeHasChild(self, node, name):
965         assert isinstance(name, unicode)
966         d = node.list()
967         def _check(children):
968             self.failIf(name in children)
969         d.addCallback(_check)
970         return d
971
972     def failUnlessChildContentsAre(self, node, name, expected_contents):
973         assert isinstance(name, unicode)
974         d = node.get_child_at_path(name)
975         d.addCallback(lambda node: node.download_to_data())
976         def _check(contents):
977             self.failUnlessEqual(contents, expected_contents)
978         d.addCallback(_check)
979         return d
980
981     def failUnlessChildURIIs(self, node, name, expected_uri):
982         assert isinstance(name, unicode)
983         d = node.get_child_at_path(name)
984         def _check(child):
985             self.failUnlessEqual(child.get_uri(), expected_uri.strip())
986         d.addCallback(_check)
987         return d
988
989     def failUnlessURIMatchesChild(self, got_uri, node, name):
990         assert isinstance(name, unicode)
991         d = node.get_child_at_path(name)
992         def _check(child):
993             self.failUnlessEqual(got_uri.strip(), child.get_uri())
994         d.addCallback(_check)
995         return d
996
997     def failUnlessCHKURIHasContents(self, got_uri, contents):
998         self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
999
1000     def test_PUT_NEWDIRURL_localdir(self):
1001         localdir = os.path.abspath("web/PUT_NEWDIRURL_local dir")
1002         # create some files there
1003         fileutil.make_dirs(os.path.join(localdir, "one"))
1004         fileutil.make_dirs(os.path.join(localdir, "one/sub"))
1005         fileutil.make_dirs(os.path.join(localdir, "two"))
1006         fileutil.make_dirs(os.path.join(localdir, "three"))
1007         self.touch(localdir, "three/foo.txt")
1008         self.touch(localdir, "three/bar.txt")
1009         self.touch(localdir, "zap.zip")
1010
1011         d = self.PUT(self.public_url + "/newdir?t=upload&localdir=%s"
1012                      % urllib.quote(localdir), "")
1013         pr = self.public_root
1014         d.addCallback(lambda res: self.failUnlessNodeHasChild(pr, u"newdir"))
1015         d.addCallback(lambda res: pr.get(u"newdir"))
1016         d.addCallback(self.failUnlessNodeKeysAre,
1017                       [u"one", u"two", u"three", u"zap.zip"])
1018         d.addCallback(lambda res: pr.get_child_at_path(u"newdir/one"))
1019         d.addCallback(self.failUnlessNodeKeysAre, [u"sub"])
1020         d.addCallback(lambda res: pr.get_child_at_path(u"newdir/three"))
1021         d.addCallback(self.failUnlessNodeKeysAre, [u"foo.txt", u"bar.txt"])
1022         d.addCallback(lambda res: pr.get_child_at_path(u"newdir/three/bar.txt"))
1023         d.addCallback(lambda barnode: barnode.download_to_data())
1024         d.addCallback(lambda contents:
1025                       self.failUnlessEqual(contents,
1026                                            "contents of three/bar.txt\n"))
1027         return d
1028
1029     def test_PUT_NEWDIRURL_localdir_disabled(self):
1030         localdir = os.path.abspath("web/PUT_NEWDIRURL_local dir_disabled")
1031         # create some files there
1032         fileutil.make_dirs(os.path.join(localdir, "one"))
1033         fileutil.make_dirs(os.path.join(localdir, "one/sub"))
1034         fileutil.make_dirs(os.path.join(localdir, "two"))
1035         fileutil.make_dirs(os.path.join(localdir, "three"))
1036         self.touch(localdir, "three/foo.txt")
1037         self.touch(localdir, "three/bar.txt")
1038         self.touch(localdir, "zap.zip")
1039
1040         self.disable_local_access()
1041         d = self.PUT(self.public_url + "/newdir?t=upload&localdir=%s"
1042                      % urllib.quote(localdir), "")
1043         d.addBoth(self.shouldFail, error.Error, "localfile disabled",
1044                   "403 Forbidden",
1045                   "local file access is disabled")
1046         return d
1047
1048     def test_PUT_NEWDIRURL_localdir_mkdirs(self):
1049         localdir = os.path.abspath("web/PUT_NEWDIRURL_local dir_mkdirs")
1050         # create some files there
1051         fileutil.make_dirs(os.path.join(localdir, "one"))
1052         fileutil.make_dirs(os.path.join(localdir, "one/sub"))
1053         fileutil.make_dirs(os.path.join(localdir, "two"))
1054         fileutil.make_dirs(os.path.join(localdir, "three"))
1055         self.touch(localdir, "three/foo.txt")
1056         self.touch(localdir, "three/bar.txt")
1057         self.touch(localdir, "zap.zip")
1058
1059         d = self.PUT(self.public_url + "/foo/subdir/newdir?t=upload&localdir=%s"
1060                      % urllib.quote(localdir),
1061                      "")
1062         fn = self._foo_node
1063         d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"subdir"))
1064         d.addCallback(lambda res: fn.get_child_at_path(u"subdir/newdir"))
1065         d.addCallback(self.failUnlessNodeKeysAre,
1066                       [u"one", u"two", u"three", u"zap.zip"])
1067         d.addCallback(lambda res: fn.get_child_at_path(u"subdir/newdir/one"))
1068         d.addCallback(self.failUnlessNodeKeysAre, [u"sub"])
1069         d.addCallback(lambda res: fn.get_child_at_path(u"subdir/newdir/three"))
1070         d.addCallback(self.failUnlessNodeKeysAre, [u"foo.txt", u"bar.txt"])
1071         d.addCallback(lambda res:
1072                       fn.get_child_at_path(u"subdir/newdir/three/bar.txt"))
1073         d.addCallback(lambda barnode: barnode.download_to_data())
1074         d.addCallback(lambda contents:
1075                       self.failUnlessEqual(contents,
1076                                            "contents of three/bar.txt\n"))
1077         return d
1078
1079     def test_PUT_NEWDIRURL_localdir_missing(self):
1080         localdir = os.path.abspath("web/PUT_NEWDIRURL_localdir_missing")
1081         # we do *not* create it, to trigger an error
1082         url = (self.public_url + "/foo/subdir/newdir?t=upload&localdir=%s"
1083                % urllib.quote(localdir))
1084         d = self.shouldHTTPError2("test_PUT_NEWDIRURL_localdir_missing",
1085                                   400, "Bad Request",
1086                                   "%s doesn't exist!" % localdir,
1087                                   self.PUT, url, "")
1088         return d
1089
1090     def test_POST_upload(self):
1091         d = self.POST(self.public_url + "/foo", t="upload",
1092                       file=("new.txt", self.NEWFILE_CONTENTS))
1093         fn = self._foo_node
1094         d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1095         d.addCallback(lambda res:
1096                       self.failUnlessChildContentsAre(fn, u"new.txt",
1097                                                       self.NEWFILE_CONTENTS))
1098         return d
1099
1100     def test_POST_upload_unicode(self):
1101         filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1102         target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1103         d = self.POST(self.public_url + "/foo", t="upload",
1104                       file=(filename, self.NEWFILE_CONTENTS))
1105         fn = self._foo_node
1106         d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1107         d.addCallback(lambda res:
1108                       self.failUnlessChildContentsAre(fn, filename,
1109                                                       self.NEWFILE_CONTENTS))
1110         d.addCallback(lambda res: self.GET(target_url))
1111         d.addCallback(lambda contents: self.failUnlessEqual(contents,
1112                                                             self.NEWFILE_CONTENTS,
1113                                                             contents))
1114         return d
1115
1116     def test_POST_upload_no_link(self):
1117         d = self.POST("/uri", t="upload",
1118                       file=("new.txt", self.NEWFILE_CONTENTS))
1119         def _check_upload_results(page):
1120             # this should be a page which describes the results of the upload
1121             # that just finished.
1122             self.failUnless("Upload Results:" in page)
1123             self.failUnless("URI:" in page)
1124             uri_re = re.compile("URI: <tt><span>(.*)</span>")
1125             mo = uri_re.search(page)
1126             self.failUnless(mo, page)
1127             new_uri = mo.group(1)
1128             return new_uri
1129         d.addCallback(_check_upload_results)
1130         d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1131         return d
1132
1133     def test_POST_upload_no_link_whendone(self):
1134         d = self.POST("/uri", t="upload", when_done="/",
1135                       file=("new.txt", self.NEWFILE_CONTENTS))
1136         d.addBoth(self.shouldRedirect, "/")
1137         return d
1138
1139     def test_POST_upload_no_link_mutable(self):
1140         d = self.POST("/uri", t="upload", mutable="true",
1141                       file=("new.txt", self.NEWFILE_CONTENTS))
1142         def _check(new_uri):
1143             new_uri = new_uri.strip()
1144             u = IURI(new_uri)
1145             self.failUnless(IMutableFileURI.providedBy(u))
1146             self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
1147             n = self.s.create_node_from_uri(new_uri)
1148             return n.download_to_data()
1149         d.addCallback(_check)
1150         def _check2(data):
1151             self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1152         d.addCallback(_check2)
1153         return d
1154
1155     def test_POST_upload_mutable(self):
1156         # this creates a mutable file
1157         d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
1158                       file=("new.txt", self.NEWFILE_CONTENTS))
1159         fn = self._foo_node
1160         d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1161         d.addCallback(lambda res:
1162                       self.failUnlessChildContentsAre(fn, u"new.txt",
1163                                                       self.NEWFILE_CONTENTS))
1164         d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1165         def _got(newnode):
1166             self.failUnless(IMutableFileNode.providedBy(newnode))
1167             self.failUnless(newnode.is_mutable())
1168             self.failIf(newnode.is_readonly())
1169             self._mutable_uri = newnode.get_uri()
1170         d.addCallback(_got)
1171
1172         # now upload it again and make sure that the URI doesn't change
1173         NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
1174         d.addCallback(lambda res:
1175                       self.POST(self.public_url + "/foo", t="upload",
1176                                 mutable="true",
1177                                 file=("new.txt", NEWER_CONTENTS)))
1178         d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1179         d.addCallback(lambda res:
1180                       self.failUnlessChildContentsAre(fn, u"new.txt",
1181                                                       NEWER_CONTENTS))
1182         d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1183         def _got2(newnode):
1184             self.failUnless(IMutableFileNode.providedBy(newnode))
1185             self.failUnless(newnode.is_mutable())
1186             self.failIf(newnode.is_readonly())
1187             self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1188         d.addCallback(_got2)
1189
1190         # finally list the directory, since mutable files are displayed
1191         # differently
1192
1193         d.addCallback(lambda res:
1194                       self.GET(self.public_url + "/foo",
1195                                followRedirect=True))
1196         def _check_page(res):
1197             # TODO: assert more about the contents
1198             self.failUnless("Overwrite" in res)
1199             self.failUnless("Choose new file:" in res)
1200             return res
1201         d.addCallback(_check_page)
1202
1203         # test that clicking on the "overwrite" button works
1204         EVEN_NEWER_CONTENTS = NEWER_CONTENTS + "even newer\n"
1205         def _parse_overwrite_form_and_submit(res):
1206             OVERWRITE_FORM_RE=re.compile('<form action="([^"]*)" method="post" .*<input type="hidden" name="t" value="overwrite" /><input type="hidden" name="name" value="([^"]*)" /><input type="hidden" name="when_done" value="([^"]*)" />', re.I)
1207             mo = OVERWRITE_FORM_RE.search(res)
1208             self.failUnless(mo)
1209             formaction=mo.group(1)
1210             formname=mo.group(2)
1211             formwhendone=mo.group(3)
1212
1213             if formaction == ".":
1214                 formaction = self.public_url + "/foo"
1215             return self.POST(formaction, t="overwrite", name=formname, when_done=formwhendone, file=("new.txt", EVEN_NEWER_CONTENTS), followRedirect=False)
1216         d.addCallback(_parse_overwrite_form_and_submit)
1217         d.addBoth(self.shouldRedirect, urllib.quote(self.public_url + "/foo/"))
1218         d.addCallback(lambda res:
1219                       self.failUnlessChildContentsAre(fn, u"new.txt",
1220                                                       EVEN_NEWER_CONTENTS))
1221         d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1222         def _got3(newnode):
1223             self.failUnless(IMutableFileNode.providedBy(newnode))
1224             self.failUnless(newnode.is_mutable())
1225             self.failIf(newnode.is_readonly())
1226             self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1227         d.addCallback(_got3)
1228
1229         return d
1230
1231     def test_POST_upload_replace(self):
1232         d = self.POST(self.public_url + "/foo", t="upload",
1233                       file=("bar.txt", self.NEWFILE_CONTENTS))
1234         fn = self._foo_node
1235         d.addCallback(self.failUnlessURIMatchesChild, fn, u"bar.txt")
1236         d.addCallback(lambda res:
1237                       self.failUnlessChildContentsAre(fn, u"bar.txt",
1238                                                       self.NEWFILE_CONTENTS))
1239         return d
1240
1241     def test_POST_upload_no_replace_ok(self):
1242         d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1243                       file=("new.txt", self.NEWFILE_CONTENTS))
1244         d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
1245         d.addCallback(lambda res: self.failUnlessEqual(res,
1246                                                        self.NEWFILE_CONTENTS))
1247         return d
1248
1249     def test_POST_upload_no_replace_queryarg(self):
1250         d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1251                       file=("bar.txt", self.NEWFILE_CONTENTS))
1252         d.addBoth(self.shouldFail, error.Error,
1253                   "POST_upload_no_replace_queryarg",
1254                   "409 Conflict",
1255                   "There was already a child by that name, and you asked me "
1256                   "to not replace it")
1257         d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1258         d.addCallback(self.failUnlessIsBarDotTxt)
1259         return d
1260
1261     def test_POST_upload_no_replace_field(self):
1262         d = self.POST(self.public_url + "/foo", t="upload", replace="false",
1263                       file=("bar.txt", self.NEWFILE_CONTENTS))
1264         d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
1265                   "409 Conflict",
1266                   "There was already a child by that name, and you asked me "
1267                   "to not replace it")
1268         d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1269         d.addCallback(self.failUnlessIsBarDotTxt)
1270         return d
1271
1272     def test_POST_upload_whendone(self):
1273         d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
1274                       file=("new.txt", self.NEWFILE_CONTENTS))
1275         d.addBoth(self.shouldRedirect, "/THERE")
1276         fn = self._foo_node
1277         d.addCallback(lambda res:
1278                       self.failUnlessChildContentsAre(fn, u"new.txt",
1279                                                       self.NEWFILE_CONTENTS))
1280         return d
1281
1282     def test_POST_upload_named(self):
1283         fn = self._foo_node
1284         d = self.POST(self.public_url + "/foo", t="upload",
1285                       name="new.txt", file=self.NEWFILE_CONTENTS)
1286         d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1287         d.addCallback(lambda res:
1288                       self.failUnlessChildContentsAre(fn, u"new.txt",
1289                                                       self.NEWFILE_CONTENTS))
1290         return d
1291
1292     def test_POST_upload_named_badfilename(self):
1293         d = self.POST(self.public_url + "/foo", t="upload",
1294                       name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
1295         d.addBoth(self.shouldFail, error.Error,
1296                   "test_POST_upload_named_badfilename",
1297                   "400 Bad Request",
1298                   "name= may not contain a slash",
1299                   )
1300         # make sure that nothing was added
1301         d.addCallback(lambda res:
1302                       self.failUnlessNodeKeysAre(self._foo_node,
1303                                                  [u"bar.txt", u"blockingfile",
1304                                                   u"empty", u"n\u00fc.txt",
1305                                                   u"sub"]))
1306         return d
1307
1308     def test_POST_mkdir(self): # return value?
1309         d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
1310         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1311         d.addCallback(self.failUnlessNodeKeysAre, [])
1312         return d
1313
1314     def test_POST_mkdir_no_parentdir_noredirect(self):
1315         d = self.POST("/uri?t=mkdir")
1316         def _after_mkdir(res):
1317             uri.NewDirectoryURI.init_from_string(res)
1318         d.addCallback(_after_mkdir)
1319         return d
1320
1321     def test_POST_mkdir_no_parentdir_redirect(self):
1322         d = self.POST("/uri?t=mkdir&redirect_to_result=true")
1323         d.addBoth(self.shouldRedirect, None, statuscode='303')
1324         def _check_target(target):
1325             target = urllib.unquote(target)
1326             self.failUnless(target.startswith("uri/URI:DIR2:"), target)
1327         d.addCallback(_check_target)
1328         return d
1329
1330     def test_POST_noparent_bad(self):
1331         d = self.shouldHTTPError2("POST /uri?t=bogus", 400, "Bad Request",
1332                                   "/uri accepts only PUT, PUT?t=mkdir, "
1333                                   "POST?t=upload, and POST?t=mkdir",
1334                                   self.POST, "/uri?t=bogus")
1335         return d
1336
1337     def test_welcome_page_mkdir_button(self):
1338         # Fetch the welcome page.
1339         d = self.GET("/")
1340         def _after_get_welcome_page(res):
1341             MKDIR_BUTTON_RE=re.compile('<form action="([^"]*)" method="post".*<input type="hidden" name="t" value="([^"]*)" /><input type="hidden" name="([^"]*)" value="([^"]*)" /><input type="submit" value="Create Directory!" />', re.I)
1342             mo = MKDIR_BUTTON_RE.search(res)
1343             formaction = mo.group(1)
1344             formt = mo.group(2)
1345             formaname = mo.group(3)
1346             formavalue = mo.group(4)
1347             return (formaction, formt, formaname, formavalue)
1348         d.addCallback(_after_get_welcome_page)
1349         def _after_parse_form(res):
1350             (formaction, formt, formaname, formavalue) = res
1351             return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
1352         d.addCallback(_after_parse_form)
1353         d.addBoth(self.shouldRedirect, None, statuscode='303')
1354         return d
1355
1356     def test_POST_mkdir_replace(self): # return value?
1357         d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
1358         d.addCallback(lambda res: self._foo_node.get(u"sub"))
1359         d.addCallback(self.failUnlessNodeKeysAre, [])
1360         return d
1361
1362     def test_POST_mkdir_no_replace_queryarg(self): # return value?
1363         d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
1364         d.addBoth(self.shouldFail, error.Error,
1365                   "POST_mkdir_no_replace_queryarg",
1366                   "409 Conflict",
1367                   "There was already a child by that name, and you asked me "
1368                   "to not replace it")
1369         d.addCallback(lambda res: self._foo_node.get(u"sub"))
1370         d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1371         return d
1372
1373     def test_POST_mkdir_no_replace_field(self): # return value?
1374         d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
1375                       replace="false")
1376         d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
1377                   "409 Conflict",
1378                   "There was already a child by that name, and you asked me "
1379                   "to not replace it")
1380         d.addCallback(lambda res: self._foo_node.get(u"sub"))
1381         d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1382         return d
1383
1384     def test_POST_mkdir_whendone_field(self):
1385         d = self.POST(self.public_url + "/foo",
1386                       t="mkdir", name="newdir", when_done="/THERE")
1387         d.addBoth(self.shouldRedirect, "/THERE")
1388         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1389         d.addCallback(self.failUnlessNodeKeysAre, [])
1390         return d
1391
1392     def test_POST_mkdir_whendone_queryarg(self):
1393         d = self.POST(self.public_url + "/foo?when_done=/THERE",
1394                       t="mkdir", name="newdir")
1395         d.addBoth(self.shouldRedirect, "/THERE")
1396         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1397         d.addCallback(self.failUnlessNodeKeysAre, [])
1398         return d
1399
1400     def test_POST_bad_t(self):
1401         d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
1402                              "BAD t=BOGUS",
1403                              self.POST, self.public_url + "/foo", t="BOGUS")
1404         return d
1405
1406     def test_POST_set_children(self):
1407         contents9, n9, newuri9 = self.makefile(9)
1408         contents10, n10, newuri10 = self.makefile(10)
1409         contents11, n11, newuri11 = self.makefile(11)
1410
1411         reqbody = """{
1412                      "atomic_added_1": [ "filenode", { "rw_uri": "%s",
1413                                                 "size": 0,
1414                                                 "metadata": {
1415                                                   "ctime": 1002777696.7564139,
1416                                                   "mtime": 1002777696.7564139
1417                                                  }
1418                                                } ],
1419                      "atomic_added_2": [ "filenode", { "rw_uri": "%s",
1420                                                 "size": 1,
1421                                                 "metadata": {
1422                                                   "ctime": 1002777696.7564139,
1423                                                   "mtime": 1002777696.7564139
1424                                                  }
1425                                                } ],
1426                      "atomic_added_3": [ "filenode", { "rw_uri": "%s",
1427                                                 "size": 2,
1428                                                 "metadata": {
1429                                                   "ctime": 1002777696.7564139,
1430                                                   "mtime": 1002777696.7564139
1431                                                  }
1432                                                } ]
1433                     }""" % (newuri9, newuri10, newuri11)
1434
1435         url = self.webish_url + self.public_url + "/foo" + "?t=set_children"
1436
1437         d = client.getPage(url, method="POST", postdata=reqbody)
1438         def _then(res):
1439             self.failUnlessURIMatchesChild(newuri9, self._foo_node, u"atomic_added_1")
1440             self.failUnlessURIMatchesChild(newuri10, self._foo_node, u"atomic_added_2")
1441             self.failUnlessURIMatchesChild(newuri11, self._foo_node, u"atomic_added_3")
1442
1443         d.addCallback(_then)
1444         return d
1445
1446     def test_POST_put_uri(self):
1447         contents, n, newuri = self.makefile(8)
1448         d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
1449         d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
1450         d.addCallback(lambda res:
1451                       self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1452                                                       contents))
1453         return d
1454
1455     def test_POST_put_uri_replace(self):
1456         contents, n, newuri = self.makefile(8)
1457         d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
1458         d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
1459         d.addCallback(lambda res:
1460                       self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1461                                                       contents))
1462         return d
1463
1464     def test_POST_put_uri_no_replace_queryarg(self):
1465         contents, n, newuri = self.makefile(8)
1466         d = self.POST(self.public_url + "/foo?replace=false", t="uri",
1467                       name="bar.txt", uri=newuri)
1468         d.addBoth(self.shouldFail, error.Error,
1469                   "POST_put_uri_no_replace_queryarg",
1470                   "409 Conflict",
1471                   "There was already a child by that name, and you asked me "
1472                   "to not replace it")
1473         d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1474         d.addCallback(self.failUnlessIsBarDotTxt)
1475         return d
1476
1477     def test_POST_put_uri_no_replace_field(self):
1478         contents, n, newuri = self.makefile(8)
1479         d = self.POST(self.public_url + "/foo", t="uri", replace="false",
1480                       name="bar.txt", uri=newuri)
1481         d.addBoth(self.shouldFail, error.Error,
1482                   "POST_put_uri_no_replace_field",
1483                   "409 Conflict",
1484                   "There was already a child by that name, and you asked me "
1485                   "to not replace it")
1486         d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1487         d.addCallback(self.failUnlessIsBarDotTxt)
1488         return d
1489
1490     def test_POST_delete(self):
1491         d = self.POST(self.public_url + "/foo", t="delete", name="bar.txt")
1492         d.addCallback(lambda res: self._foo_node.list())
1493         def _check(children):
1494             self.failIf(u"bar.txt" in children)
1495         d.addCallback(_check)
1496         return d
1497
1498     def test_POST_rename_file(self):
1499         d = self.POST(self.public_url + "/foo", t="rename",
1500                       from_name="bar.txt", to_name='wibble.txt')
1501         d.addCallback(lambda res:
1502                       self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1503         d.addCallback(lambda res:
1504                       self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
1505         d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
1506         d.addCallback(self.failUnlessIsBarDotTxt)
1507         d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
1508         d.addCallback(self.failUnlessIsBarJSON)
1509         return d
1510
1511     def test_POST_rename_file_replace(self):
1512         # rename a file and replace a directory with it
1513         d = self.POST(self.public_url + "/foo", t="rename",
1514                       from_name="bar.txt", to_name='empty')
1515         d.addCallback(lambda res:
1516                       self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1517         d.addCallback(lambda res:
1518                       self.failUnlessNodeHasChild(self._foo_node, u"empty"))
1519         d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
1520         d.addCallback(self.failUnlessIsBarDotTxt)
1521         d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
1522         d.addCallback(self.failUnlessIsBarJSON)
1523         return d
1524
1525     def test_POST_rename_file_no_replace_queryarg(self):
1526         # rename a file and replace a directory with it
1527         d = self.POST(self.public_url + "/foo?replace=false", t="rename",
1528                       from_name="bar.txt", to_name='empty')
1529         d.addBoth(self.shouldFail, error.Error,
1530                   "POST_rename_file_no_replace_queryarg",
1531                   "409 Conflict",
1532                   "There was already a child by that name, and you asked me "
1533                   "to not replace it")
1534         d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
1535         d.addCallback(self.failUnlessIsEmptyJSON)
1536         return d
1537
1538     def test_POST_rename_file_no_replace_field(self):
1539         # rename a file and replace a directory with it
1540         d = self.POST(self.public_url + "/foo", t="rename", replace="false",
1541                       from_name="bar.txt", to_name='empty')
1542         d.addBoth(self.shouldFail, error.Error,
1543                   "POST_rename_file_no_replace_field",
1544                   "409 Conflict",
1545                   "There was already a child by that name, and you asked me "
1546                   "to not replace it")
1547         d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
1548         d.addCallback(self.failUnlessIsEmptyJSON)
1549         return d
1550
1551     def failUnlessIsEmptyJSON(self, res):
1552         data = simplejson.loads(res)
1553         self.failUnlessEqual(data[0], "dirnode", data)
1554         self.failUnlessEqual(len(data[1]["children"]), 0)
1555
1556     def test_POST_rename_file_slash_fail(self):
1557         d = self.POST(self.public_url + "/foo", t="rename",
1558                       from_name="bar.txt", to_name='kirk/spock.txt')
1559         d.addBoth(self.shouldFail, error.Error,
1560                   "test_POST_rename_file_slash_fail",
1561                   "400 Bad Request",
1562                   "to_name= may not contain a slash",
1563                   )
1564         d.addCallback(lambda res:
1565                       self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
1566         d.addCallback(lambda res: self.POST(self.public_url, t="rename",
1567                       from_name="foo/bar.txt", to_name='george.txt'))
1568         d.addBoth(self.shouldFail, error.Error,
1569                   "test_POST_rename_file_slash_fail",
1570                   "400 Bad Request",
1571                   "from_name= may not contain a slash",
1572                   )
1573         d.addCallback(lambda res:
1574                       self.failUnlessNodeHasChild(self.public_root, u"foo"))
1575         d.addCallback(lambda res:
1576                       self.failIfNodeHasChild(self.public_root, u"george.txt"))
1577         d.addCallback(lambda res:
1578                       self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
1579         d.addCallback(lambda res: self.GET(self.public_url + "/foo?t=json"))
1580         d.addCallback(self.failUnlessIsFooJSON)
1581         return d
1582
1583     def test_POST_rename_dir(self):
1584         d = self.POST(self.public_url, t="rename",
1585                       from_name="foo", to_name='plunk')
1586         d.addCallback(lambda res:
1587                       self.failIfNodeHasChild(self.public_root, u"foo"))
1588         d.addCallback(lambda res:
1589                       self.failUnlessNodeHasChild(self.public_root, u"plunk"))
1590         d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
1591         d.addCallback(self.failUnlessIsFooJSON)
1592         return d
1593
1594     def shouldRedirect(self, res, target=None, statuscode=None):
1595         """ If target is not None then the redirection has to go to target.  If
1596         statuscode is not None then the redirection has to be accomplished with
1597         that HTTP status code."""
1598         if not isinstance(res, failure.Failure):
1599             self.fail("we were expecting to get redirected %s, not get an"
1600                       " actual page: %s" % ((target is None) and "somewhere" or ("to " + target), res))
1601         res.trap(error.PageRedirect)
1602         if statuscode is not None:
1603             self.failUnlessEqual(res.value.status, statuscode)
1604         if target is not None:
1605             # the PageRedirect does not seem to capture the uri= query arg
1606             # properly, so we can't check for it.
1607             realtarget = self.webish_url + target
1608             self.failUnlessEqual(res.value.location, realtarget)
1609         return res.value.location
1610
1611     def test_GET_URI_form(self):
1612         base = "/uri?uri=%s" % self._bar_txt_uri
1613         # this is supposed to give us a redirect to /uri/$URI, plus arguments
1614         targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
1615         d = self.GET(base)
1616         d.addBoth(self.shouldRedirect, targetbase)
1617         d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
1618         d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
1619         d.addCallback(lambda res: self.GET(base+"&t=json"))
1620         d.addBoth(self.shouldRedirect, targetbase+"?t=json")
1621         d.addCallback(self.log, "about to get file by uri")
1622         d.addCallback(lambda res: self.GET(base, followRedirect=True))
1623         d.addCallback(self.failUnlessIsBarDotTxt)
1624         d.addCallback(self.log, "got file by uri, about to get dir by uri")
1625         d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
1626                                            followRedirect=True))
1627         d.addCallback(self.failUnlessIsFooJSON)
1628         d.addCallback(self.log, "got dir by uri")
1629
1630         return d
1631
1632     def test_GET_rename_form(self):
1633         d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
1634                      followRedirect=True) # XXX [ ] todo: figure out why '.../foo' doesn't work
1635         def _check(res):
1636             self.failUnless(re.search(r'name="when_done" value=".*%s/foo/' % (urllib.quote(self.public_url),), res), (r'name="when_done" value=".*%s/foo/' % (urllib.quote(self.public_url),), res,))
1637             self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
1638         d.addCallback(_check)
1639         return d
1640
1641     def log(self, res, msg):
1642         #print "MSG: %s  RES: %s" % (msg, res)
1643         log.msg(msg)
1644         return res
1645
1646     def test_GET_URI_URL(self):
1647         base = "/uri/%s" % self._bar_txt_uri
1648         d = self.GET(base)
1649         d.addCallback(self.failUnlessIsBarDotTxt)
1650         d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
1651         d.addCallback(self.failUnlessIsBarDotTxt)
1652         d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
1653         d.addCallback(self.failUnlessIsBarDotTxt)
1654         return d
1655
1656     def test_GET_URI_URL_dir(self):
1657         base = "/uri/%s?t=json" % self._foo_uri
1658         d = self.GET(base)
1659         d.addCallback(self.failUnlessIsFooJSON)
1660         return d
1661
1662     def test_GET_URI_URL_missing(self):
1663         base = "/uri/%s" % self._bad_file_uri
1664         d = self.GET(base)
1665         d.addBoth(self.shouldHTTPError, "test_GET_URI_URL_missing",
1666                   http.GONE, response_substring="NotEnoughSharesError")
1667         # TODO: how can we exercise both sides of WebDownloadTarget.fail
1668         # here? we must arrange for a download to fail after target.open()
1669         # has been called, and then inspect the response to see that it is
1670         # shorter than we expected.
1671         return d
1672
1673     def test_PUT_NEWFILEURL_uri(self):
1674         contents, n, new_uri = self.makefile(8)
1675         d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
1676         d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
1677         d.addCallback(lambda res:
1678                       self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1679                                                       contents))
1680         return d
1681
1682     def test_PUT_NEWFILEURL_uri_replace(self):
1683         contents, n, new_uri = self.makefile(8)
1684         d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
1685         d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
1686         d.addCallback(lambda res:
1687                       self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1688                                                       contents))
1689         return d
1690
1691     def test_PUT_NEWFILEURL_uri_no_replace(self):
1692         contents, n, new_uri = self.makefile(8)
1693         d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
1694         d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_uri_no_replace",
1695                   "409 Conflict",
1696                   "There was already a child by that name, and you asked me "
1697                   "to not replace it")
1698         return d
1699
1700     def test_PUT_NEWFILE_URI(self):
1701         file_contents = "New file contents here\n"
1702         d = self.PUT("/uri", file_contents)
1703         def _check(uri):
1704             self.failUnless(uri in FakeCHKFileNode.all_contents)
1705             self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
1706                                  file_contents)
1707             return self.GET("/uri/%s" % uri)
1708         d.addCallback(_check)
1709         def _check2(res):
1710             self.failUnlessEqual(res, file_contents)
1711         d.addCallback(_check2)
1712         return d
1713
1714     def test_PUT_NEWFILE_URI_only_PUT(self):
1715         d = self.PUT("/uri?t=bogus", "")
1716         d.addBoth(self.shouldFail, error.Error,
1717                   "PUT_NEWFILE_URI_only_PUT",
1718                   "400 Bad Request",
1719                   "/uri only accepts PUT and PUT?t=mkdir")
1720         return d
1721
1722     def test_PUT_NEWFILE_URI_mutable(self):
1723         file_contents = "New file contents here\n"
1724         d = self.PUT("/uri?mutable=true", file_contents)
1725         def _check_mutable(uri):
1726             uri = uri.strip()
1727             u = IURI(uri)
1728             self.failUnless(IMutableFileURI.providedBy(u))
1729             self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
1730             n = self.s.create_node_from_uri(uri)
1731             return n.download_to_data()
1732         d.addCallback(_check_mutable)
1733         def _check2_mutable(data):
1734             self.failUnlessEqual(data, file_contents)
1735         d.addCallback(_check2_mutable)
1736         return d
1737
1738         def _check(uri):
1739             self.failUnless(uri in FakeCHKFileNode.all_contents)
1740             self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
1741                                  file_contents)
1742             return self.GET("/uri/%s" % uri)
1743         d.addCallback(_check)
1744         def _check2(res):
1745             self.failUnlessEqual(res, file_contents)
1746         d.addCallback(_check2)
1747         return d
1748
1749     def test_PUT_mkdir(self):
1750         d = self.PUT("/uri?t=mkdir", "")
1751         def _check(uri):
1752             n = self.s.create_node_from_uri(uri.strip())
1753             d2 = self.failUnlessNodeKeysAre(n, [])
1754             d2.addCallback(lambda res:
1755                            self.GET("/uri/%s?t=json" % uri))
1756             return d2
1757         d.addCallback(_check)
1758         d.addCallback(self.failUnlessIsEmptyJSON)
1759         return d
1760
1761     def test_POST_check(self):
1762         d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
1763         def _done(res):
1764             # this returns a string form of the results, which are probably
1765             # None since we're using fake filenodes.
1766             # TODO: verify that the check actually happened, by changing
1767             # FakeCHKFileNode to count how many times .check() has been
1768             # called.
1769             pass
1770         d.addCallback(_done)
1771         return d
1772
1773     def test_bad_method(self):
1774         url = self.webish_url + self.public_url + "/foo/bar.txt"
1775         d = self.shouldHTTPError2("test_bad_method", 404, "Not Found", None,
1776                                   client.getPage, url, method="BOGUS")
1777         return d
1778
1779     def test_short_url(self):
1780         url = self.webish_url + "/uri"
1781         d = self.shouldHTTPError2("test_short_url", 404, "Not Found", None,
1782                                   client.getPage, url, method="DELETE")
1783         return d