]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/test/test_web.py
#514: improve test coverage
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / test / test_web.py
1 import re, urllib
2 import simplejson
3 from twisted.application import service
4 from twisted.trial import unittest
5 from twisted.internet import defer, reactor
6 from twisted.web import client, error, http
7 from twisted.python import failure, log
8 from allmydata import interfaces, provisioning, uri, webish
9 from allmydata.immutable import upload, download
10 from allmydata.web import status, common
11 from allmydata.util import fileutil, testutil
12 from allmydata.test.common import FakeDirectoryNode, FakeCHKFileNode, \
13      FakeMutableFileNode, create_chk_filenode
14 from allmydata.interfaces import IURI, INewDirectoryURI, \
15      IReadonlyNewDirectoryURI, IFileURI, IMutableFileURI, IMutableFileNode
16 from allmydata.mutable import servermap, publish, retrieve
17
18 # create a fake uploader/downloader, and a couple of fake dirnodes, then
19 # create a webserver that works against them
20
21 class FakeIntroducerClient:
22     def get_all_connectors(self):
23         return {}
24     def get_all_connections_for(self, service_name):
25         return frozenset()
26     def get_all_peerids(self):
27         return frozenset()
28
29 class FakeClient(service.MultiService):
30     nodeid = "fake_nodeid"
31     nickname = "fake_nickname"
32     basedir = "fake_basedir"
33     def get_versions(self):
34         return {'allmydata': "fake",
35                 'foolscap': "fake",
36                 'twisted': "fake",
37                 'zfec': "fake",
38                 }
39     introducer_furl = "None"
40     introducer_client = FakeIntroducerClient()
41     _all_upload_status = [upload.UploadStatus()]
42     _all_download_status = [download.DownloadStatus()]
43     _all_mapupdate_statuses = [servermap.UpdateStatus()]
44     _all_publish_statuses = [publish.PublishStatus()]
45     _all_retrieve_statuses = [retrieve.RetrieveStatus()]
46     convergence = "some random string"
47
48     def connected_to_introducer(self):
49         return False
50
51     def create_node_from_uri(self, auri):
52         u = uri.from_string(auri)
53         if (INewDirectoryURI.providedBy(u)
54             or IReadonlyNewDirectoryURI.providedBy(u)):
55             return FakeDirectoryNode(self).init_from_uri(u)
56         if IFileURI.providedBy(u):
57             return FakeCHKFileNode(u, self)
58         assert IMutableFileURI.providedBy(u), u
59         return FakeMutableFileNode(self).init_from_uri(u)
60
61     def create_empty_dirnode(self):
62         n = FakeDirectoryNode(self)
63         d = n.create()
64         d.addCallback(lambda res: n)
65         return d
66
67     MUTABLE_SIZELIMIT = FakeMutableFileNode.MUTABLE_SIZELIMIT
68     def create_mutable_file(self, contents=""):
69         n = FakeMutableFileNode(self)
70         return n.create(contents)
71
72     def upload(self, uploadable):
73         d = uploadable.get_size()
74         d.addCallback(lambda size: uploadable.read(size))
75         def _got_data(datav):
76             data = "".join(datav)
77             n = create_chk_filenode(self, data)
78             results = upload.UploadResults()
79             results.uri = n.get_uri()
80             return results
81         d.addCallback(_got_data)
82         return d
83
84     def list_all_upload_statuses(self):
85         return self._all_upload_status
86     def list_all_download_statuses(self):
87         return self._all_download_status
88     def list_all_mapupdate_statuses(self):
89         return self._all_mapupdate_statuses
90     def list_all_publish_statuses(self):
91         return self._all_publish_statuses
92     def list_all_retrieve_statuses(self):
93         return self._all_retrieve_statuses
94     def list_all_helper_statuses(self):
95         return []
96
97 class HTTPClientHEADFactory(client.HTTPClientFactory):
98     def __init__(self, *args, **kwargs):
99         client.HTTPClientFactory.__init__(self, *args, **kwargs)
100         self.deferred.addCallback(lambda res: self.response_headers)
101
102     def noPage(self, reason):
103         # Twisted-2.5.0 and earlier had a bug, in which they would raise an
104         # exception when the response to a HEAD request had no body (when in
105         # fact they are defined to never have a body). This was fixed in
106         # Twisted-8.0 . To work around this, we catch the
107         # PartialDownloadError and make it disappear.
108         if (reason.check(client.PartialDownloadError)
109             and self.method.upper() == "HEAD"):
110             self.page("")
111             return
112         return client.HTTPClientFactory.noPage(self, reason)
113
114
115 class WebMixin(object):
116     def setUp(self):
117         self.s = FakeClient()
118         self.s.startService()
119         self.ws = s = webish.WebishServer("0")
120         s.setServiceParent(self.s)
121         self.webish_port = port = s.listener._port.getHost().port
122         self.webish_url = "http://localhost:%d" % port
123
124         l = [ self.s.create_empty_dirnode() for x in range(6) ]
125         d = defer.DeferredList(l)
126         def _then(res):
127             self.public_root = res[0][1]
128             assert interfaces.IDirectoryNode.providedBy(self.public_root), res
129             self.public_url = "/uri/" + self.public_root.get_uri()
130             self.private_root = res[1][1]
131
132             foo = res[2][1]
133             self._foo_node = foo
134             self._foo_uri = foo.get_uri()
135             self._foo_readonly_uri = foo.get_readonly_uri()
136             # NOTE: we ignore the deferred on all set_uri() calls, because we
137             # know the fake nodes do these synchronously
138             self.public_root.set_uri(u"foo", foo.get_uri())
139
140             self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
141             foo.set_uri(u"bar.txt", self._bar_txt_uri)
142
143             foo.set_uri(u"empty", res[3][1].get_uri())
144             sub_uri = res[4][1].get_uri()
145             self._sub_uri = sub_uri
146             foo.set_uri(u"sub", sub_uri)
147             sub = self.s.create_node_from_uri(sub_uri)
148
149             _ign, n, blocking_uri = self.makefile(1)
150             foo.set_uri(u"blockingfile", blocking_uri)
151
152             unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
153             # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
154             # still think of it as an umlaut
155             foo.set_uri(unicode_filename, self._bar_txt_uri)
156
157             _ign, n, baz_file = self.makefile(2)
158             sub.set_uri(u"baz.txt", baz_file)
159
160             _ign, n, self._bad_file_uri = self.makefile(3)
161             # this uri should not be downloadable
162             del FakeCHKFileNode.all_contents[self._bad_file_uri]
163
164             rodir = res[5][1]
165             self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri())
166             rodir.set_uri(u"nor", baz_file)
167
168             # public/
169             # public/foo/
170             # public/foo/bar.txt
171             # public/foo/blockingfile
172             # public/foo/empty/
173             # public/foo/sub/
174             # public/foo/sub/baz.txt
175             # public/reedownlee/
176             # public/reedownlee/nor
177             self.NEWFILE_CONTENTS = "newfile contents\n"
178
179             return foo.get_metadata_for(u"bar.txt")
180         d.addCallback(_then)
181         def _got_metadata(metadata):
182             self._bar_txt_metadata = metadata
183         d.addCallback(_got_metadata)
184         return d
185
186     def makefile(self, number):
187         contents = "contents of file %s\n" % number
188         n = create_chk_filenode(self.s, contents)
189         return contents, n, n.get_uri()
190
191     def tearDown(self):
192         return self.s.stopService()
193
194     def failUnlessIsBarDotTxt(self, res):
195         self.failUnlessEqual(res, self.BAR_CONTENTS, res)
196
197     def failUnlessIsBarJSON(self, res):
198         data = simplejson.loads(res)
199         self.failUnless(isinstance(data, list))
200         self.failUnlessEqual(data[0], u"filenode")
201         self.failUnless(isinstance(data[1], dict))
202         self.failIf(data[1]["mutable"])
203         self.failIf("rw_uri" in data[1]) # immutable
204         self.failUnlessEqual(data[1]["ro_uri"], self._bar_txt_uri)
205         self.failUnlessEqual(data[1]["size"], len(self.BAR_CONTENTS))
206
207     def failUnlessIsFooJSON(self, res):
208         data = simplejson.loads(res)
209         self.failUnless(isinstance(data, list))
210         self.failUnlessEqual(data[0], "dirnode", res)
211         self.failUnless(isinstance(data[1], dict))
212         self.failUnless(data[1]["mutable"])
213         self.failUnless("rw_uri" in data[1]) # mutable
214         self.failUnlessEqual(data[1]["rw_uri"], self._foo_uri)
215         self.failUnlessEqual(data[1]["ro_uri"], self._foo_readonly_uri)
216
217         kidnames = sorted([unicode(n) for n in data[1]["children"]])
218         self.failUnlessEqual(kidnames,
219                              [u"bar.txt", u"blockingfile", u"empty",
220                               u"n\u00fc.txt", u"sub"])
221         kids = dict( [(unicode(name),value)
222                       for (name,value)
223                       in data[1]["children"].iteritems()] )
224         self.failUnlessEqual(kids[u"sub"][0], "dirnode")
225         self.failUnless("metadata" in kids[u"sub"][1])
226         self.failUnless("ctime" in kids[u"sub"][1]["metadata"])
227         self.failUnless("mtime" in kids[u"sub"][1]["metadata"])
228         self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
229         self.failUnlessEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
230         self.failUnlessEqual(kids[u"bar.txt"][1]["ro_uri"], self._bar_txt_uri)
231         self.failUnlessEqual(kids[u"bar.txt"][1]["metadata"]["ctime"],
232                              self._bar_txt_metadata["ctime"])
233         self.failUnlessEqual(kids[u"n\u00fc.txt"][1]["ro_uri"],
234                              self._bar_txt_uri)
235
236     def GET(self, urlpath, followRedirect=False):
237         assert not isinstance(urlpath, unicode)
238         url = self.webish_url + urlpath
239         return client.getPage(url, method="GET", followRedirect=followRedirect)
240
241     def HEAD(self, urlpath):
242         # this requires some surgery, because twisted.web.client doesn't want
243         # to give us back the response headers.
244         factory = HTTPClientHEADFactory(urlpath, method="HEAD")
245         reactor.connectTCP("localhost", self.webish_port, factory)
246         return factory.deferred
247
248     def PUT(self, urlpath, data):
249         url = self.webish_url + urlpath
250         return client.getPage(url, method="PUT", postdata=data)
251
252     def DELETE(self, urlpath):
253         url = self.webish_url + urlpath
254         return client.getPage(url, method="DELETE")
255
256     def POST(self, urlpath, followRedirect=False, **fields):
257         url = self.webish_url + urlpath
258         sepbase = "boogabooga"
259         sep = "--" + sepbase
260         form = []
261         form.append(sep)
262         form.append('Content-Disposition: form-data; name="_charset"')
263         form.append('')
264         form.append('UTF-8')
265         form.append(sep)
266         for name, value in fields.iteritems():
267             if isinstance(value, tuple):
268                 filename, value = value
269                 form.append('Content-Disposition: form-data; name="%s"; '
270                             'filename="%s"' % (name, filename.encode("utf-8")))
271             else:
272                 form.append('Content-Disposition: form-data; name="%s"' % name)
273             form.append('')
274             if isinstance(value, unicode):
275                 value = value.encode("utf-8")
276             else:
277                 value = str(value)
278             assert isinstance(value, str)
279             form.append(value)
280             form.append(sep)
281         form[-1] += "--"
282         body = "\r\n".join(form) + "\r\n"
283         headers = {"content-type": "multipart/form-data; boundary=%s" % sepbase,
284                    }
285         return client.getPage(url, method="POST", postdata=body,
286                               headers=headers, followRedirect=followRedirect)
287
288     def shouldFail(self, res, expected_failure, which,
289                    substring=None, response_substring=None):
290         if isinstance(res, failure.Failure):
291             res.trap(expected_failure)
292             if substring:
293                 self.failUnless(substring in str(res),
294                                 "substring '%s' not in '%s'"
295                                 % (substring, str(res)))
296             if response_substring:
297                 self.failUnless(response_substring in res.value.response,
298                                 "response substring '%s' not in '%s'"
299                                 % (response_substring, res.value.response))
300         else:
301             self.fail("%s was supposed to raise %s, not get '%s'" %
302                       (which, expected_failure, res))
303
304     def shouldFail2(self, expected_failure, which, substring,
305                     response_substring,
306                     callable, *args, **kwargs):
307         assert substring is None or isinstance(substring, str)
308         assert response_substring is None or isinstance(response_substring, str)
309         d = defer.maybeDeferred(callable, *args, **kwargs)
310         def done(res):
311             if isinstance(res, failure.Failure):
312                 res.trap(expected_failure)
313                 if substring:
314                     self.failUnless(substring in str(res),
315                                     "%s: substring '%s' not in '%s'"
316                                     % (which, substring, str(res)))
317                 if response_substring:
318                     self.failUnless(response_substring in res.value.response,
319                                     "%s: response substring '%s' not in '%s'"
320                                     % (which,
321                                        response_substring, res.value.response))
322             else:
323                 self.fail("%s was supposed to raise %s, not get '%s'" %
324                           (which, expected_failure, res))
325         d.addBoth(done)
326         return d
327
328     def should404(self, res, which):
329         if isinstance(res, failure.Failure):
330             res.trap(error.Error)
331             self.failUnlessEqual(res.value.status, "404")
332         else:
333             self.fail("%s was supposed to Error(404), not get '%s'" %
334                       (which, res))
335
336     def shouldHTTPError(self, res, which, code=None, substring=None,
337                         response_substring=None):
338         if isinstance(res, failure.Failure):
339             res.trap(error.Error)
340             if code is not None:
341                 self.failUnlessEqual(res.value.status, str(code))
342             if substring:
343                 self.failUnless(substring in str(res),
344                                 "substring '%s' not in '%s'"
345                                 % (substring, str(res)))
346             if response_substring:
347                 self.failUnless(response_substring in res.value.response,
348                                 "response substring '%s' not in '%s'"
349                                 % (response_substring, res.value.response))
350         else:
351             self.fail("%s was supposed to Error(%s), not get '%s'" %
352                       (which, code, res))
353
354     def shouldHTTPError2(self, which,
355                          code=None, substring=None, response_substring=None,
356                          callable=None, *args, **kwargs):
357         assert substring is None or isinstance(substring, str)
358         assert callable
359         d = defer.maybeDeferred(callable, *args, **kwargs)
360         d.addBoth(self.shouldHTTPError, which,
361                   code, substring, response_substring)
362         return d
363
364
365 class Web(WebMixin, testutil.StallMixin, unittest.TestCase):
366     def test_create(self):
367         pass
368
369     def test_welcome(self):
370         d = self.GET("/")
371         def _check(res):
372             self.failUnless('Welcome To AllMyData' in res)
373             self.failUnless('Tahoe' in res)
374
375             self.s.basedir = 'web/test_welcome'
376             fileutil.make_dirs("web/test_welcome")
377             fileutil.make_dirs("web/test_welcome/private")
378             return self.GET("/")
379         d.addCallback(_check)
380         return d
381
382     def test_provisioning_math(self):
383         self.failUnlessEqual(provisioning.binomial(10, 0), 1)
384         self.failUnlessEqual(provisioning.binomial(10, 1), 10)
385         self.failUnlessEqual(provisioning.binomial(10, 2), 45)
386         self.failUnlessEqual(provisioning.binomial(10, 9), 10)
387         self.failUnlessEqual(provisioning.binomial(10, 10), 1)
388
389     def test_provisioning(self):
390         d = self.GET("/provisioning/")
391         def _check(res):
392             self.failUnless('Tahoe Provisioning Tool' in res)
393             fields = {'filled': True,
394                       "num_users": int(50e3),
395                       "files_per_user": 1000,
396                       "space_per_user": int(1e9),
397                       "sharing_ratio": 1.0,
398                       "encoding_parameters": "3-of-10-5",
399                       "num_servers": 30,
400                       "ownership_mode": "A",
401                       "download_rate": 100,
402                       "upload_rate": 10,
403                       "delete_rate": 10,
404                       "lease_timer": 7,
405                       }
406             return self.POST("/provisioning/", **fields)
407
408         d.addCallback(_check)
409         def _check2(res):
410             self.failUnless('Tahoe Provisioning Tool' in res)
411             self.failUnless("Share space consumed: 167.01TB" in res)
412
413             fields = {'filled': True,
414                       "num_users": int(50e6),
415                       "files_per_user": 1000,
416                       "space_per_user": int(5e9),
417                       "sharing_ratio": 1.0,
418                       "encoding_parameters": "25-of-100-50",
419                       "num_servers": 30000,
420                       "ownership_mode": "E",
421                       "drive_failure_model": "U",
422                       "drive_size": 1000,
423                       "download_rate": 1000,
424                       "upload_rate": 100,
425                       "delete_rate": 100,
426                       "lease_timer": 7,
427                       }
428             return self.POST("/provisioning/", **fields)
429         d.addCallback(_check2)
430         def _check3(res):
431             self.failUnless("Share space consumed: huge!" in res)
432             fields = {'filled': True}
433             return self.POST("/provisioning/", **fields)
434         d.addCallback(_check3)
435         def _check4(res):
436             self.failUnless("Share space consumed:" in res)
437         d.addCallback(_check4)
438         return d
439
440     def test_status(self):
441         dl_num = self.s.list_all_download_statuses()[0].get_counter()
442         ul_num = self.s.list_all_upload_statuses()[0].get_counter()
443         mu_num = self.s.list_all_mapupdate_statuses()[0].get_counter()
444         pub_num = self.s.list_all_publish_statuses()[0].get_counter()
445         ret_num = self.s.list_all_retrieve_statuses()[0].get_counter()
446         d = self.GET("/status", followRedirect=True)
447         def _check(res):
448             self.failUnless('Upload and Download Status' in res, res)
449             self.failUnless('"down-%d"' % dl_num in res, res)
450             self.failUnless('"up-%d"' % ul_num in res, res)
451             self.failUnless('"mapupdate-%d"' % mu_num in res, res)
452             self.failUnless('"publish-%d"' % pub_num in res, res)
453             self.failUnless('"retrieve-%d"' % ret_num in res, res)
454         d.addCallback(_check)
455         d.addCallback(lambda res: self.GET("/status/?t=json"))
456         def _check_json(res):
457             data = simplejson.loads(res)
458             self.failUnless(isinstance(data, dict))
459             active = data["active"]
460             # TODO: test more. We need a way to fake an active operation
461             # here.
462         d.addCallback(_check_json)
463
464         d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
465         def _check_dl(res):
466             self.failUnless("File Download Status" in res, res)
467         d.addCallback(_check_dl)
468         d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
469         def _check_ul(res):
470             self.failUnless("File Upload Status" in res, res)
471         d.addCallback(_check_ul)
472         d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num))
473         def _check_mapupdate(res):
474             self.failUnless("Mutable File Servermap Update Status" in res, res)
475         d.addCallback(_check_mapupdate)
476         d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num))
477         def _check_publish(res):
478             self.failUnless("Mutable File Publish Status" in res, res)
479         d.addCallback(_check_publish)
480         d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num))
481         def _check_retrieve(res):
482             self.failUnless("Mutable File Retrieve Status" in res, res)
483         d.addCallback(_check_retrieve)
484
485         return d
486
487     def test_status_numbers(self):
488         drrm = status.DownloadResultsRendererMixin()
489         self.failUnlessEqual(drrm.render_time(None, None), "")
490         self.failUnlessEqual(drrm.render_time(None, 2.5), "2.50s")
491         self.failUnlessEqual(drrm.render_time(None, 0.25), "250ms")
492         self.failUnlessEqual(drrm.render_time(None, 0.0021), "2.1ms")
493         self.failUnlessEqual(drrm.render_time(None, 0.000123), "123us")
494         self.failUnlessEqual(drrm.render_rate(None, None), "")
495         self.failUnlessEqual(drrm.render_rate(None, 2500000), "2.50MBps")
496         self.failUnlessEqual(drrm.render_rate(None, 30100), "30.1kBps")
497         self.failUnlessEqual(drrm.render_rate(None, 123), "123Bps")
498
499         urrm = status.UploadResultsRendererMixin()
500         self.failUnlessEqual(urrm.render_time(None, None), "")
501         self.failUnlessEqual(urrm.render_time(None, 2.5), "2.50s")
502         self.failUnlessEqual(urrm.render_time(None, 0.25), "250ms")
503         self.failUnlessEqual(urrm.render_time(None, 0.0021), "2.1ms")
504         self.failUnlessEqual(urrm.render_time(None, 0.000123), "123us")
505         self.failUnlessEqual(urrm.render_rate(None, None), "")
506         self.failUnlessEqual(urrm.render_rate(None, 2500000), "2.50MBps")
507         self.failUnlessEqual(urrm.render_rate(None, 30100), "30.1kBps")
508         self.failUnlessEqual(urrm.render_rate(None, 123), "123Bps")
509
510     def test_GET_FILEURL(self):
511         d = self.GET(self.public_url + "/foo/bar.txt")
512         d.addCallback(self.failUnlessIsBarDotTxt)
513         return d
514
515     def test_HEAD_FILEURL(self):
516         d = self.HEAD(self.public_url + "/foo/bar.txt")
517         def _got(headers):
518             self.failUnlessEqual(headers["content-length"][0],
519                                  str(len(self.BAR_CONTENTS)))
520             self.failUnlessEqual(headers["content-type"], ["text/plain"])
521         d.addCallback(_got)
522         return d
523
524     def test_GET_FILEURL_named(self):
525         base = "/file/%s" % urllib.quote(self._bar_txt_uri)
526         base2 = "/named/%s" % urllib.quote(self._bar_txt_uri)
527         d = self.GET(base + "/@@name=/blah.txt")
528         d.addCallback(self.failUnlessIsBarDotTxt)
529         d.addCallback(lambda res: self.GET(base + "/blah.txt"))
530         d.addCallback(self.failUnlessIsBarDotTxt)
531         d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt"))
532         d.addCallback(self.failUnlessIsBarDotTxt)
533         d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt"))
534         d.addCallback(self.failUnlessIsBarDotTxt)
535         save_url = base + "?save=true&filename=blah.txt"
536         d.addCallback(lambda res: self.GET(save_url))
537         d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
538         u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
539         u_fn_e = urllib.quote(u_filename.encode("utf-8"))
540         u_url = base + "?save=true&filename=" + u_fn_e
541         d.addCallback(lambda res: self.GET(u_url))
542         d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
543         return d
544
545     def test_PUT_FILEURL_named_bad(self):
546         base = "/file/%s" % urllib.quote(self._bar_txt_uri)
547         d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad",
548                              "400 Bad Request",
549                              "/file can only be used with GET or HEAD",
550                              self.PUT, base + "/@@name=/blah.txt", "")
551         return d
552
553     def test_GET_DIRURL_named_bad(self):
554         base = "/file/%s" % urllib.quote(self._foo_uri)
555         d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
556                              "400 Bad Request",
557                              "is not a file-cap",
558                              self.GET, base + "/@@name=/blah.txt")
559         return d
560
561     def test_GET_slash_file_bad(self):
562         d = self.shouldFail2(error.Error, "test_GET_slash_file_bad",
563                              "404 Not Found",
564                              "/file must be followed by a file-cap and a name",
565                              self.GET, "/file")
566         return d
567
568     def test_GET_unhandled_URI_named(self):
569         contents, n, newuri = self.makefile(12)
570         verifier_cap = n.get_verifier().to_string()
571         base = "/file/%s" % urllib.quote(verifier_cap)
572         # client.create_node_from_uri() can't handle verify-caps
573         d = self.shouldFail2(error.Error, "GET_unhandled_URI_named",
574                              "400 Bad Request",
575                              "is not a valid file- or directory- cap",
576                              self.GET, base)
577         return d
578
579     def test_GET_unhandled_URI(self):
580         contents, n, newuri = self.makefile(12)
581         verifier_cap = n.get_verifier().to_string()
582         base = "/uri/%s" % urllib.quote(verifier_cap)
583         # client.create_node_from_uri() can't handle verify-caps
584         d = self.shouldFail2(error.Error, "test_GET_unhandled_URI",
585                              "400 Bad Request",
586                              "is not a valid file- or directory- cap",
587                              self.GET, base)
588         return d
589
590     def test_GET_FILE_URI(self):
591         base = "/uri/%s" % urllib.quote(self._bar_txt_uri)
592         d = self.GET(base)
593         d.addCallback(self.failUnlessIsBarDotTxt)
594         return d
595
596     def test_GET_FILE_URI_badchild(self):
597         base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
598         errmsg = "Files have no children, certainly not named 'boguschild'"
599         d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
600                              "400 Bad Request", errmsg,
601                              self.GET, base)
602         return d
603
604     def test_PUT_FILE_URI_badchild(self):
605         base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
606         errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory"
607         d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
608                              "400 Bad Request", errmsg,
609                              self.PUT, base, "")
610         return d
611
612     def test_GET_FILEURL_save(self):
613         d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true")
614         # TODO: look at the headers, expect a Content-Disposition: attachment
615         # header.
616         d.addCallback(self.failUnlessIsBarDotTxt)
617         return d
618
619     def test_GET_FILEURL_missing(self):
620         d = self.GET(self.public_url + "/foo/missing")
621         d.addBoth(self.should404, "test_GET_FILEURL_missing")
622         return d
623
624     def test_PUT_NEWFILEURL(self):
625         d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
626         # TODO: we lose the response code, so we can't check this
627         #self.failUnlessEqual(responsecode, 201)
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_mutable(self):
635         d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
636                      self.NEWFILE_CONTENTS)
637         # TODO: we lose the response code, so we can't check this
638         #self.failUnlessEqual(responsecode, 201)
639         def _check_uri(res):
640             u = uri.from_string_mutable_filenode(res)
641             self.failUnless(u.is_mutable())
642             self.failIf(u.is_readonly())
643             return res
644         d.addCallback(_check_uri)
645         d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
646         d.addCallback(lambda res:
647                       self.failUnlessMutableChildContentsAre(self._foo_node,
648                                                              u"new.txt",
649                                                              self.NEWFILE_CONTENTS))
650         return d
651
652     def test_PUT_NEWFILEURL_mutable_toobig(self):
653         d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_mutable_toobig",
654                              "413 Request Entity Too Large",
655                              "SDMF is limited to one segment, and 10001 > 10000",
656                              self.PUT,
657                              self.public_url + "/foo/new.txt?mutable=true",
658                              "b" * (self.s.MUTABLE_SIZELIMIT+1))
659         return d
660
661     def test_PUT_NEWFILEURL_replace(self):
662         d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
663         # TODO: we lose the response code, so we can't check this
664         #self.failUnlessEqual(responsecode, 200)
665         d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
666         d.addCallback(lambda res:
667                       self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
668                                                       self.NEWFILE_CONTENTS))
669         return d
670
671     def test_PUT_NEWFILEURL_bad_t(self):
672         d = self.shouldFail2(error.Error, "PUT_bad_t", "400 Bad Request",
673                              "PUT to a file: bad t=bogus",
674                              self.PUT, self.public_url + "/foo/bar.txt?t=bogus",
675                              "contents")
676         return d
677
678     def test_PUT_NEWFILEURL_no_replace(self):
679         d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
680                      self.NEWFILE_CONTENTS)
681         d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
682                   "409 Conflict",
683                   "There was already a child by that name, and you asked me "
684                   "to not replace it")
685         return d
686
687     def test_PUT_NEWFILEURL_mkdirs(self):
688         d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
689         fn = self._foo_node
690         d.addCallback(self.failUnlessURIMatchesChild, fn, u"newdir/new.txt")
691         d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
692         d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
693         d.addCallback(lambda res:
694                       self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
695                                                       self.NEWFILE_CONTENTS))
696         return d
697
698     def test_PUT_NEWFILEURL_blocked(self):
699         d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
700                      self.NEWFILE_CONTENTS)
701         d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
702                   "409 Conflict",
703                   "Unable to create directory 'blockingfile': a file was in the way")
704         return d
705
706     def test_DELETE_FILEURL(self):
707         d = self.DELETE(self.public_url + "/foo/bar.txt")
708         d.addCallback(lambda res:
709                       self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
710         return d
711
712     def test_DELETE_FILEURL_missing(self):
713         d = self.DELETE(self.public_url + "/foo/missing")
714         d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
715         return d
716
717     def test_DELETE_FILEURL_missing2(self):
718         d = self.DELETE(self.public_url + "/missing/missing")
719         d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
720         return d
721
722     def test_GET_FILEURL_json(self):
723         # twisted.web.http.parse_qs ignores any query args without an '=', so
724         # I can't do "GET /path?json", I have to do "GET /path/t=json"
725         # instead. This may make it tricky to emulate the S3 interface
726         # completely.
727         d = self.GET(self.public_url + "/foo/bar.txt?t=json")
728         d.addCallback(self.failUnlessIsBarJSON)
729         return d
730
731     def test_GET_FILEURL_json_missing(self):
732         d = self.GET(self.public_url + "/foo/missing?json")
733         d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
734         return d
735
736     def test_GET_FILEURL_uri(self):
737         d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
738         def _check(res):
739             self.failUnlessEqual(res, self._bar_txt_uri)
740         d.addCallback(_check)
741         d.addCallback(lambda res:
742                       self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
743         def _check2(res):
744             # for now, for files, uris and readonly-uris are the same
745             self.failUnlessEqual(res, self._bar_txt_uri)
746         d.addCallback(_check2)
747         return d
748
749     def test_GET_FILEURL_badtype(self):
750         d = self.shouldHTTPError2("GET t=bogus", 400, "Bad Request",
751                                   "bad t=bogus",
752                                   self.GET,
753                                   self.public_url + "/foo/bar.txt?t=bogus")
754         return d
755
756     def test_GET_FILEURL_uri_missing(self):
757         d = self.GET(self.public_url + "/foo/missing?t=uri")
758         d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
759         return d
760
761     def test_GET_DIRURL(self):
762         # the addSlash means we get a redirect here
763         # from /uri/$URI/foo/ , we need ../../../ to get back to the root
764         ROOT = "../../.."
765         d = self.GET(self.public_url + "/foo", followRedirect=True)
766         def _check(res):
767             self.failUnless(('<a href="%s">Return to Welcome page' % ROOT)
768                             in res, res)
769             # the FILE reference points to a URI, but it should end in bar.txt
770             bar_url = ("%s/file/%s/@@named=/bar.txt" %
771                        (ROOT, urllib.quote(self._bar_txt_uri)))
772             get_bar = "".join([r'<td>',
773                                r'<a href="%s">bar.txt</a>' % bar_url,
774                                r'</td>',
775                                r'\s+<td>FILE</td>',
776                                r'\s+<td>%d</td>' % len(self.BAR_CONTENTS),
777                                ])
778             self.failUnless(re.search(get_bar, res), res)
779             for line in res.split("\n"):
780                 # find the line that contains the delete button for bar.txt
781                 if ("form action" in line and
782                     'value="delete"' in line and
783                     'value="bar.txt"' in line):
784                     # the form target should use a relative URL
785                     foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
786                     self.failUnless(('action="%s"' % foo_url) in line, line)
787                     # and the when_done= should too
788                     #done_url = urllib.quote(???)
789                     #self.failUnless(('name="when_done" value="%s"' % done_url)
790                     #                in line, line)
791                     break
792             else:
793                 self.fail("unable to find delete-bar.txt line", res)
794
795             # the DIR reference just points to a URI
796             sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
797             get_sub = ((r'<td><a href="%s">sub</a></td>' % sub_url)
798                        + r'\s+<td>DIR</td>')
799             self.failUnless(re.search(get_sub, res), res)
800         d.addCallback(_check)
801
802         # look at a directory which is readonly
803         d.addCallback(lambda res:
804                       self.GET(self.public_url + "/reedownlee", followRedirect=True))
805         def _check2(res):
806             self.failUnless("(readonly)" in res, res)
807             self.failIf("Upload a file" in res, res)
808         d.addCallback(_check2)
809
810         # and at a directory that contains a readonly directory
811         d.addCallback(lambda res:
812                       self.GET(self.public_url, followRedirect=True))
813         def _check3(res):
814             self.failUnless(re.search(r'<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a>'
815                                       '</td>\s+<td>DIR-RO</td>', res))
816         d.addCallback(_check3)
817
818         return d
819
820     def test_GET_DIRURL_badtype(self):
821         d = self.shouldHTTPError2("test_GET_DIRURL_badtype",
822                                   400, "Bad Request",
823                                   "bad t=bogus",
824                                   self.GET,
825                                   self.public_url + "/foo?t=bogus")
826         return d
827
828     def test_GET_DIRURL_json(self):
829         d = self.GET(self.public_url + "/foo?t=json")
830         d.addCallback(self.failUnlessIsFooJSON)
831         return d
832
833
834     def test_POST_DIRURL_manifest_no_ophandle(self):
835         d = self.shouldFail2(error.Error,
836                              "test_POST_DIRURL_manifest_no_ophandle",
837                              "400 Bad Request",
838                              "slow operation requires ophandle=",
839                              self.POST, self.public_url, t="start-manifest")
840         return d
841
842     def test_POST_DIRURL_manifest(self):
843         d = defer.succeed(None)
844         def getman(ignored, output):
845             d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
846                           followRedirect=True)
847             d.addCallback(self.wait_for_operation, "125")
848             d.addCallback(self.get_operation_results, "125", output)
849             return d
850         d.addCallback(getman, None)
851         def _got_html(manifest):
852             self.failUnless("Manifest of SI=" in manifest)
853             self.failUnless("<td>sub</td>" in manifest)
854             self.failUnless(self._sub_uri in manifest)
855             self.failUnless("<td>sub/baz.txt</td>" in manifest)
856         d.addCallback(_got_html)
857         d.addCallback(getman, "html")
858         d.addCallback(_got_html)
859         d.addCallback(getman, "text")
860         def _got_text(manifest):
861             self.failUnless("\nsub " + self._sub_uri + "\n" in manifest)
862             self.failUnless("\nsub/baz.txt URI:CHK:" in manifest)
863         d.addCallback(_got_text)
864         d.addCallback(getman, "JSON")
865         def _got_json(manifest):
866             data = manifest["manifest"]
867             got = {}
868             for (path_list, cap) in data:
869                 got[tuple(path_list)] = cap
870             self.failUnlessEqual(got[(u"sub",)], self._sub_uri)
871             self.failUnless((u"sub",u"baz.txt") in got)
872         d.addCallback(_got_json)
873         return d
874
875     def test_POST_DIRURL_deepsize_no_ophandle(self):
876         d = self.shouldFail2(error.Error,
877                              "test_POST_DIRURL_deepsize_no_ophandle",
878                              "400 Bad Request",
879                              "slow operation requires ophandle=",
880                              self.POST, self.public_url, t="start-deep-size")
881         return d
882
883     def test_POST_DIRURL_deepsize(self):
884         d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
885                       followRedirect=True)
886         d.addCallback(self.wait_for_operation, "126")
887         d.addCallback(self.get_operation_results, "126", "json")
888         def _got_json(data):
889             self.failUnlessEqual(data["finished"], True)
890             size = data["size"]
891             self.failUnless(size > 1000)
892         d.addCallback(_got_json)
893         d.addCallback(self.get_operation_results, "126", "text")
894         def _got_text(res):
895             mo = re.search(r'^size: (\d+)$', res, re.M)
896             self.failUnless(mo, res)
897             size = int(mo.group(1))
898             # with directories, the size varies.
899             self.failUnless(size > 1000)
900         d.addCallback(_got_text)
901         return d
902
903     def test_POST_DIRURL_deepstats_no_ophandle(self):
904         d = self.shouldFail2(error.Error,
905                              "test_POST_DIRURL_deepstats_no_ophandle",
906                              "400 Bad Request",
907                              "slow operation requires ophandle=",
908                              self.POST, self.public_url, t="start-deep-stats")
909         return d
910
911     def test_POST_DIRURL_deepstats(self):
912         d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
913                       followRedirect=True)
914         d.addCallback(self.wait_for_operation, "127")
915         d.addCallback(self.get_operation_results, "127", "json")
916         def _got_json(stats):
917             expected = {"count-immutable-files": 3,
918                         "count-mutable-files": 0,
919                         "count-literal-files": 0,
920                         "count-files": 3,
921                         "count-directories": 3,
922                         "size-immutable-files": 57,
923                         "size-literal-files": 0,
924                         #"size-directories": 1912, # varies
925                         #"largest-directory": 1590,
926                         "largest-directory-children": 5,
927                         "largest-immutable-file": 19,
928                         }
929             for k,v in expected.iteritems():
930                 self.failUnlessEqual(stats[k], v,
931                                      "stats[%s] was %s, not %s" %
932                                      (k, stats[k], v))
933             self.failUnlessEqual(stats["size-files-histogram"],
934                                  [ [11, 31, 3] ])
935         d.addCallback(_got_json)
936         return d
937
938     def test_GET_DIRURL_uri(self):
939         d = self.GET(self.public_url + "/foo?t=uri")
940         def _check(res):
941             self.failUnlessEqual(res, self._foo_uri)
942         d.addCallback(_check)
943         return d
944
945     def test_GET_DIRURL_readonly_uri(self):
946         d = self.GET(self.public_url + "/foo?t=readonly-uri")
947         def _check(res):
948             self.failUnlessEqual(res, self._foo_readonly_uri)
949         d.addCallback(_check)
950         return d
951
952     def test_PUT_NEWDIRURL(self):
953         d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
954         d.addCallback(lambda res:
955                       self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
956         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
957         d.addCallback(self.failUnlessNodeKeysAre, [])
958         return d
959
960     def test_PUT_NEWDIRURL_exists(self):
961         d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
962         d.addCallback(lambda res:
963                       self.failUnlessNodeHasChild(self._foo_node, u"sub"))
964         d.addCallback(lambda res: self._foo_node.get(u"sub"))
965         d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
966         return d
967
968     def test_PUT_NEWDIRURL_blocked(self):
969         d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
970                              "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
971                              self.PUT,
972                              self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
973         d.addCallback(lambda res:
974                       self.failUnlessNodeHasChild(self._foo_node, u"sub"))
975         d.addCallback(lambda res: self._foo_node.get(u"sub"))
976         d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
977         return d
978
979     def test_PUT_NEWDIRURL_mkdir_p(self):
980         d = defer.succeed(None)
981         d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
982         d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
983         d.addCallback(lambda res: self._foo_node.get(u"mkp"))
984         def mkdir_p(mkpnode):
985             url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
986             d = self.POST(url)
987             def made_subsub(ssuri):
988                 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
989                 d.addCallback(lambda ssnode: self.failUnlessEqual(ssnode.get_uri(), ssuri))
990                 d = self.POST(url)
991                 d.addCallback(lambda uri2: self.failUnlessEqual(uri2, ssuri))
992                 return d
993             d.addCallback(made_subsub)
994             return d
995         d.addCallback(mkdir_p)
996         return d
997
998     def test_PUT_NEWDIRURL_mkdirs(self):
999         d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1000         d.addCallback(lambda res:
1001                       self.failIfNodeHasChild(self._foo_node, u"newdir"))
1002         d.addCallback(lambda res:
1003                       self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1004         d.addCallback(lambda res:
1005                       self._foo_node.get_child_at_path(u"subdir/newdir"))
1006         d.addCallback(self.failUnlessNodeKeysAre, [])
1007         return d
1008
1009     def test_DELETE_DIRURL(self):
1010         d = self.DELETE(self.public_url + "/foo")
1011         d.addCallback(lambda res:
1012                       self.failIfNodeHasChild(self.public_root, u"foo"))
1013         return d
1014
1015     def test_DELETE_DIRURL_missing(self):
1016         d = self.DELETE(self.public_url + "/foo/missing")
1017         d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
1018         d.addCallback(lambda res:
1019                       self.failUnlessNodeHasChild(self.public_root, u"foo"))
1020         return d
1021
1022     def test_DELETE_DIRURL_missing2(self):
1023         d = self.DELETE(self.public_url + "/missing")
1024         d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
1025         return d
1026
1027     def dump_root(self):
1028         print "NODEWALK"
1029         w = webish.DirnodeWalkerMixin()
1030         def visitor(childpath, childnode, metadata):
1031             print childpath
1032         d = w.walk(self.public_root, visitor)
1033         return d
1034
1035     def failUnlessNodeKeysAre(self, node, expected_keys):
1036         for k in expected_keys:
1037             assert isinstance(k, unicode)
1038         d = node.list()
1039         def _check(children):
1040             self.failUnlessEqual(sorted(children.keys()), sorted(expected_keys))
1041         d.addCallback(_check)
1042         return d
1043     def failUnlessNodeHasChild(self, node, name):
1044         assert isinstance(name, unicode)
1045         d = node.list()
1046         def _check(children):
1047             self.failUnless(name in children)
1048         d.addCallback(_check)
1049         return d
1050     def failIfNodeHasChild(self, node, name):
1051         assert isinstance(name, unicode)
1052         d = node.list()
1053         def _check(children):
1054             self.failIf(name in children)
1055         d.addCallback(_check)
1056         return d
1057
1058     def failUnlessChildContentsAre(self, node, name, expected_contents):
1059         assert isinstance(name, unicode)
1060         d = node.get_child_at_path(name)
1061         d.addCallback(lambda node: node.download_to_data())
1062         def _check(contents):
1063             self.failUnlessEqual(contents, expected_contents)
1064         d.addCallback(_check)
1065         return d
1066
1067     def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
1068         assert isinstance(name, unicode)
1069         d = node.get_child_at_path(name)
1070         d.addCallback(lambda node: node.download_best_version())
1071         def _check(contents):
1072             self.failUnlessEqual(contents, expected_contents)
1073         d.addCallback(_check)
1074         return d
1075
1076     def failUnlessChildURIIs(self, node, name, expected_uri):
1077         assert isinstance(name, unicode)
1078         d = node.get_child_at_path(name)
1079         def _check(child):
1080             self.failUnlessEqual(child.get_uri(), expected_uri.strip())
1081         d.addCallback(_check)
1082         return d
1083
1084     def failUnlessURIMatchesChild(self, got_uri, node, name):
1085         assert isinstance(name, unicode)
1086         d = node.get_child_at_path(name)
1087         def _check(child):
1088             self.failUnlessEqual(got_uri.strip(), child.get_uri())
1089         d.addCallback(_check)
1090         return d
1091
1092     def failUnlessCHKURIHasContents(self, got_uri, contents):
1093         self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
1094
1095     def test_POST_upload(self):
1096         d = self.POST(self.public_url + "/foo", t="upload",
1097                       file=("new.txt", self.NEWFILE_CONTENTS))
1098         fn = self._foo_node
1099         d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1100         d.addCallback(lambda res:
1101                       self.failUnlessChildContentsAre(fn, u"new.txt",
1102                                                       self.NEWFILE_CONTENTS))
1103         return d
1104
1105     def test_POST_upload_unicode(self):
1106         filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1107         d = self.POST(self.public_url + "/foo", t="upload",
1108                       file=(filename, self.NEWFILE_CONTENTS))
1109         fn = self._foo_node
1110         d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1111         d.addCallback(lambda res:
1112                       self.failUnlessChildContentsAre(fn, filename,
1113                                                       self.NEWFILE_CONTENTS))
1114         target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1115         d.addCallback(lambda res: self.GET(target_url))
1116         d.addCallback(lambda contents: self.failUnlessEqual(contents,
1117                                                             self.NEWFILE_CONTENTS,
1118                                                             contents))
1119         return d
1120
1121     def test_POST_upload_unicode_named(self):
1122         filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1123         d = self.POST(self.public_url + "/foo", t="upload",
1124                       name=filename,
1125                       file=("overridden", self.NEWFILE_CONTENTS))
1126         fn = self._foo_node
1127         d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1128         d.addCallback(lambda res:
1129                       self.failUnlessChildContentsAre(fn, filename,
1130                                                       self.NEWFILE_CONTENTS))
1131         target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1132         d.addCallback(lambda res: self.GET(target_url))
1133         d.addCallback(lambda contents: self.failUnlessEqual(contents,
1134                                                             self.NEWFILE_CONTENTS,
1135                                                             contents))
1136         return d
1137
1138     def test_POST_upload_no_link(self):
1139         d = self.POST("/uri", t="upload",
1140                       file=("new.txt", self.NEWFILE_CONTENTS))
1141         def _check_upload_results(page):
1142             # this should be a page which describes the results of the upload
1143             # that just finished.
1144             self.failUnless("Upload Results:" in page)
1145             self.failUnless("URI:" in page)
1146             uri_re = re.compile("URI: <tt><span>(.*)</span>")
1147             mo = uri_re.search(page)
1148             self.failUnless(mo, page)
1149             new_uri = mo.group(1)
1150             return new_uri
1151         d.addCallback(_check_upload_results)
1152         d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1153         return d
1154
1155     def test_POST_upload_no_link_whendone(self):
1156         d = self.POST("/uri", t="upload", when_done="/",
1157                       file=("new.txt", self.NEWFILE_CONTENTS))
1158         d.addBoth(self.shouldRedirect, "/")
1159         return d
1160
1161     def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
1162         d = defer.maybeDeferred(callable, *args, **kwargs)
1163         def done(res):
1164             if isinstance(res, failure.Failure):
1165                 res.trap(error.PageRedirect)
1166                 statuscode = res.value.status
1167                 target = res.value.location
1168                 return checker(statuscode, target)
1169             self.fail("%s: callable was supposed to redirect, not return '%s'"
1170                       % (which, res))
1171         d.addBoth(done)
1172         return d
1173
1174     def test_POST_upload_no_link_whendone_results(self):
1175         def check(statuscode, target):
1176             self.failUnlessEqual(statuscode, str(http.FOUND))
1177             self.failUnless(target.startswith(self.webish_url), target)
1178             return client.getPage(target, method="GET")
1179         d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
1180                                  check,
1181                                  self.POST, "/uri", t="upload",
1182                                  when_done="/uri/%(uri)s",
1183                                  file=("new.txt", self.NEWFILE_CONTENTS))
1184         d.addCallback(lambda res:
1185                       self.failUnlessEqual(res, self.NEWFILE_CONTENTS))
1186         return d
1187
1188     def test_POST_upload_no_link_mutable(self):
1189         d = self.POST("/uri", t="upload", mutable="true",
1190                       file=("new.txt", self.NEWFILE_CONTENTS))
1191         def _check(new_uri):
1192             new_uri = new_uri.strip()
1193             self.new_uri = new_uri
1194             u = IURI(new_uri)
1195             self.failUnless(IMutableFileURI.providedBy(u))
1196             self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
1197             n = self.s.create_node_from_uri(new_uri)
1198             return n.download_best_version()
1199         d.addCallback(_check)
1200         def _check2(data):
1201             self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1202             return self.GET("/uri/%s" % urllib.quote(self.new_uri))
1203         d.addCallback(_check2)
1204         def _check3(data):
1205             self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1206             return self.GET("/file/%s" % urllib.quote(self.new_uri))
1207         d.addCallback(_check3)
1208         def _check4(data):
1209             self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1210         d.addCallback(_check4)
1211         return d
1212
1213     def test_POST_upload_no_link_mutable_toobig(self):
1214         d = self.shouldFail2(error.Error,
1215                              "test_POST_upload_no_link_mutable_toobig",
1216                              "413 Request Entity Too Large",
1217                              "SDMF is limited to one segment, and 10001 > 10000",
1218                              self.POST,
1219                              "/uri", t="upload", mutable="true",
1220                              file=("new.txt",
1221                                    "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1222         return d
1223
1224     def test_POST_upload_mutable(self):
1225         # this creates a mutable file
1226         d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
1227                       file=("new.txt", self.NEWFILE_CONTENTS))
1228         fn = self._foo_node
1229         d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1230         d.addCallback(lambda res:
1231                       self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1232                                                              self.NEWFILE_CONTENTS))
1233         d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1234         def _got(newnode):
1235             self.failUnless(IMutableFileNode.providedBy(newnode))
1236             self.failUnless(newnode.is_mutable())
1237             self.failIf(newnode.is_readonly())
1238             self._mutable_node = newnode
1239             self._mutable_uri = newnode.get_uri()
1240         d.addCallback(_got)
1241
1242         # now upload it again and make sure that the URI doesn't change
1243         NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
1244         d.addCallback(lambda res:
1245                       self.POST(self.public_url + "/foo", t="upload",
1246                                 mutable="true",
1247                                 file=("new.txt", NEWER_CONTENTS)))
1248         d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1249         d.addCallback(lambda res:
1250                       self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1251                                                              NEWER_CONTENTS))
1252         d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1253         def _got2(newnode):
1254             self.failUnless(IMutableFileNode.providedBy(newnode))
1255             self.failUnless(newnode.is_mutable())
1256             self.failIf(newnode.is_readonly())
1257             self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1258         d.addCallback(_got2)
1259
1260         # upload a second time, using PUT instead of POST
1261         NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
1262         d.addCallback(lambda res:
1263                       self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
1264         d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1265         d.addCallback(lambda res:
1266                       self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1267                                                              NEW2_CONTENTS))
1268
1269         # finally list the directory, since mutable files are displayed
1270         # slightly differently
1271
1272         d.addCallback(lambda res:
1273                       self.GET(self.public_url + "/foo/",
1274                                followRedirect=True))
1275         def _check_page(res):
1276             # TODO: assert more about the contents
1277             self.failUnless("SSK" in res)
1278             return res
1279         d.addCallback(_check_page)
1280
1281         d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1282         def _got3(newnode):
1283             self.failUnless(IMutableFileNode.providedBy(newnode))
1284             self.failUnless(newnode.is_mutable())
1285             self.failIf(newnode.is_readonly())
1286             self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1287         d.addCallback(_got3)
1288
1289         # look at the JSON form of the enclosing directory
1290         d.addCallback(lambda res:
1291                       self.GET(self.public_url + "/foo/?t=json",
1292                                followRedirect=True))
1293         def _check_page_json(res):
1294             parsed = simplejson.loads(res)
1295             self.failUnlessEqual(parsed[0], "dirnode")
1296             children = dict( [(unicode(name),value)
1297                               for (name,value)
1298                               in parsed[1]["children"].iteritems()] )
1299             self.failUnless("new.txt" in children)
1300             new_json = children["new.txt"]
1301             self.failUnlessEqual(new_json[0], "filenode")
1302             self.failUnless(new_json[1]["mutable"])
1303             self.failUnlessEqual(new_json[1]["rw_uri"], self._mutable_uri)
1304             ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1305             self.failUnlessEqual(new_json[1]["ro_uri"], ro_uri)
1306         d.addCallback(_check_page_json)
1307
1308         # and the JSON form of the file
1309         d.addCallback(lambda res:
1310                       self.GET(self.public_url + "/foo/new.txt?t=json"))
1311         def _check_file_json(res):
1312             parsed = simplejson.loads(res)
1313             self.failUnlessEqual(parsed[0], "filenode")
1314             self.failUnless(parsed[1]["mutable"])
1315             self.failUnlessEqual(parsed[1]["rw_uri"], self._mutable_uri)
1316             ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1317             self.failUnlessEqual(parsed[1]["ro_uri"], ro_uri)
1318         d.addCallback(_check_file_json)
1319
1320         # and look at t=uri and t=readonly-uri
1321         d.addCallback(lambda res:
1322                       self.GET(self.public_url + "/foo/new.txt?t=uri"))
1323         d.addCallback(lambda res: self.failUnlessEqual(res, self._mutable_uri))
1324         d.addCallback(lambda res:
1325                       self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
1326         def _check_ro_uri(res):
1327             ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1328             self.failUnlessEqual(res, ro_uri)
1329         d.addCallback(_check_ro_uri)
1330
1331         # make sure we can get to it from /uri/URI
1332         d.addCallback(lambda res:
1333                       self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
1334         d.addCallback(lambda res:
1335                       self.failUnlessEqual(res, NEW2_CONTENTS))
1336
1337         # and that HEAD computes the size correctly
1338         d.addCallback(lambda res:
1339                       self.HEAD(self.public_url + "/foo/new.txt"))
1340         def _got_headers(headers):
1341             self.failUnlessEqual(headers["content-length"][0],
1342                                  str(len(NEW2_CONTENTS)))
1343             self.failUnlessEqual(headers["content-type"], ["text/plain"])
1344         d.addCallback(_got_headers)
1345
1346         # make sure that size errors are displayed correctly for overwrite
1347         d.addCallback(lambda res:
1348                       self.shouldFail2(error.Error,
1349                                        "test_POST_upload_mutable-toobig",
1350                                        "413 Request Entity Too Large",
1351                                        "SDMF is limited to one segment, and 10001 > 10000",
1352                                        self.POST,
1353                                        self.public_url + "/foo", t="upload",
1354                                        mutable="true",
1355                                        file=("new.txt",
1356                                              "b" * (self.s.MUTABLE_SIZELIMIT+1)),
1357                                        ))
1358
1359         d.addErrback(self.dump_error)
1360         return d
1361
1362     def test_POST_upload_mutable_toobig(self):
1363         d = self.shouldFail2(error.Error,
1364                              "test_POST_upload_no_link_mutable_toobig",
1365                              "413 Request Entity Too Large",
1366                              "SDMF is limited to one segment, and 10001 > 10000",
1367                              self.POST,
1368                              self.public_url + "/foo",
1369                              t="upload", mutable="true",
1370                              file=("new.txt",
1371                                    "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1372         return d
1373
1374     def dump_error(self, f):
1375         # if the web server returns an error code (like 400 Bad Request),
1376         # web.client.getPage puts the HTTP response body into the .response
1377         # attribute of the exception object that it gives back. It does not
1378         # appear in the Failure's repr(), so the ERROR that trial displays
1379         # will be rather terse and unhelpful. addErrback this method to the
1380         # end of your chain to get more information out of these errors.
1381         if f.check(error.Error):
1382             print "web.error.Error:"
1383             print f
1384             print f.value.response
1385         return f
1386
1387     def test_POST_upload_replace(self):
1388         d = self.POST(self.public_url + "/foo", t="upload",
1389                       file=("bar.txt", self.NEWFILE_CONTENTS))
1390         fn = self._foo_node
1391         d.addCallback(self.failUnlessURIMatchesChild, fn, u"bar.txt")
1392         d.addCallback(lambda res:
1393                       self.failUnlessChildContentsAre(fn, u"bar.txt",
1394                                                       self.NEWFILE_CONTENTS))
1395         return d
1396
1397     def test_POST_upload_no_replace_ok(self):
1398         d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1399                       file=("new.txt", self.NEWFILE_CONTENTS))
1400         d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
1401         d.addCallback(lambda res: self.failUnlessEqual(res,
1402                                                        self.NEWFILE_CONTENTS))
1403         return d
1404
1405     def test_POST_upload_no_replace_queryarg(self):
1406         d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1407                       file=("bar.txt", self.NEWFILE_CONTENTS))
1408         d.addBoth(self.shouldFail, error.Error,
1409                   "POST_upload_no_replace_queryarg",
1410                   "409 Conflict",
1411                   "There was already a child by that name, and you asked me "
1412                   "to not replace it")
1413         d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1414         d.addCallback(self.failUnlessIsBarDotTxt)
1415         return d
1416
1417     def test_POST_upload_no_replace_field(self):
1418         d = self.POST(self.public_url + "/foo", t="upload", replace="false",
1419                       file=("bar.txt", self.NEWFILE_CONTENTS))
1420         d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
1421                   "409 Conflict",
1422                   "There was already a child by that name, and you asked me "
1423                   "to not replace it")
1424         d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1425         d.addCallback(self.failUnlessIsBarDotTxt)
1426         return d
1427
1428     def test_POST_upload_whendone(self):
1429         d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
1430                       file=("new.txt", self.NEWFILE_CONTENTS))
1431         d.addBoth(self.shouldRedirect, "/THERE")
1432         fn = self._foo_node
1433         d.addCallback(lambda res:
1434                       self.failUnlessChildContentsAre(fn, u"new.txt",
1435                                                       self.NEWFILE_CONTENTS))
1436         return d
1437
1438     def test_POST_upload_named(self):
1439         fn = self._foo_node
1440         d = self.POST(self.public_url + "/foo", t="upload",
1441                       name="new.txt", file=self.NEWFILE_CONTENTS)
1442         d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1443         d.addCallback(lambda res:
1444                       self.failUnlessChildContentsAre(fn, u"new.txt",
1445                                                       self.NEWFILE_CONTENTS))
1446         return d
1447
1448     def test_POST_upload_named_badfilename(self):
1449         d = self.POST(self.public_url + "/foo", t="upload",
1450                       name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
1451         d.addBoth(self.shouldFail, error.Error,
1452                   "test_POST_upload_named_badfilename",
1453                   "400 Bad Request",
1454                   "name= may not contain a slash",
1455                   )
1456         # make sure that nothing was added
1457         d.addCallback(lambda res:
1458                       self.failUnlessNodeKeysAre(self._foo_node,
1459                                                  [u"bar.txt", u"blockingfile",
1460                                                   u"empty", u"n\u00fc.txt",
1461                                                   u"sub"]))
1462         return d
1463
1464     def test_POST_FILEURL_check(self):
1465         bar_url = self.public_url + "/foo/bar.txt"
1466         d = self.POST(bar_url, t="check")
1467         def _check(res):
1468             self.failUnless("Healthy!" in res)
1469         d.addCallback(_check)
1470         redir_url = "http://allmydata.org/TARGET"
1471         def _check2(statuscode, target):
1472             self.failUnlessEqual(statuscode, str(http.FOUND))
1473             self.failUnlessEqual(target, redir_url)
1474         d.addCallback(lambda res:
1475                       self.shouldRedirect2("test_POST_FILEURL_check",
1476                                            _check2,
1477                                            self.POST, bar_url,
1478                                            t="check",
1479                                            when_done=redir_url))
1480         d.addCallback(lambda res:
1481                       self.POST(bar_url, t="check", return_to=redir_url))
1482         def _check3(res):
1483             self.failUnless("Healthy!" in res)
1484             self.failUnless("Return to parent directory" in res)
1485             self.failUnless(redir_url in res)
1486         d.addCallback(_check3)
1487         return d
1488
1489     def test_POST_FILEURL_check_and_repair(self):
1490         bar_url = self.public_url + "/foo/bar.txt"
1491         d = self.POST(bar_url, t="check", repair="true")
1492         def _check(res):
1493             self.failUnless("Healthy!" in res)
1494         d.addCallback(_check)
1495         redir_url = "http://allmydata.org/TARGET"
1496         def _check2(statuscode, target):
1497             self.failUnlessEqual(statuscode, str(http.FOUND))
1498             self.failUnlessEqual(target, redir_url)
1499         d.addCallback(lambda res:
1500                       self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
1501                                            _check2,
1502                                            self.POST, bar_url,
1503                                            t="check", repair="true",
1504                                            when_done=redir_url))
1505         d.addCallback(lambda res:
1506                       self.POST(bar_url, t="check", return_to=redir_url))
1507         def _check3(res):
1508             self.failUnless("Healthy!" in res)
1509             self.failUnless("Return to parent directory" in res)
1510             self.failUnless(redir_url in res)
1511         d.addCallback(_check3)
1512         return d
1513
1514     def test_POST_DIRURL_check(self):
1515         foo_url = self.public_url + "/foo/"
1516         d = self.POST(foo_url, t="check")
1517         def _check(res):
1518             self.failUnless("Healthy!" in res)
1519         d.addCallback(_check)
1520         redir_url = "http://allmydata.org/TARGET"
1521         def _check2(statuscode, target):
1522             self.failUnlessEqual(statuscode, str(http.FOUND))
1523             self.failUnlessEqual(target, redir_url)
1524         d.addCallback(lambda res:
1525                       self.shouldRedirect2("test_POST_DIRURL_check",
1526                                            _check2,
1527                                            self.POST, foo_url,
1528                                            t="check",
1529                                            when_done=redir_url))
1530         d.addCallback(lambda res:
1531                       self.POST(foo_url, t="check", return_to=redir_url))
1532         def _check3(res):
1533             self.failUnless("Healthy!" in res)
1534             self.failUnless("Return to parent directory" in res)
1535             self.failUnless(redir_url in res)
1536         d.addCallback(_check3)
1537         return d
1538
1539     def test_POST_DIRURL_check_and_repair(self):
1540         foo_url = self.public_url + "/foo/"
1541         d = self.POST(foo_url, t="check", repair="true")
1542         def _check(res):
1543             self.failUnless("Healthy!" in res)
1544         d.addCallback(_check)
1545         redir_url = "http://allmydata.org/TARGET"
1546         def _check2(statuscode, target):
1547             self.failUnlessEqual(statuscode, str(http.FOUND))
1548             self.failUnlessEqual(target, redir_url)
1549         d.addCallback(lambda res:
1550                       self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
1551                                            _check2,
1552                                            self.POST, foo_url,
1553                                            t="check", repair="true",
1554                                            when_done=redir_url))
1555         d.addCallback(lambda res:
1556                       self.POST(foo_url, t="check", return_to=redir_url))
1557         def _check3(res):
1558             self.failUnless("Healthy!" in res)
1559             self.failUnless("Return to parent directory" in res)
1560             self.failUnless(redir_url in res)
1561         d.addCallback(_check3)
1562         return d
1563
1564     def wait_for_operation(self, ignored, ophandle):
1565         url = "/operations/" + ophandle
1566         url += "?t=status&output=JSON"
1567         d = self.GET(url)
1568         def _got(res):
1569             data = simplejson.loads(res)
1570             if not data["finished"]:
1571                 d = self.stall(delay=1.0)
1572                 d.addCallback(self.wait_for_operation, ophandle)
1573                 return d
1574             return data
1575         d.addCallback(_got)
1576         return d
1577
1578     def get_operation_results(self, ignored, ophandle, output=None):
1579         url = "/operations/" + ophandle
1580         url += "?t=status"
1581         if output:
1582             url += "&output=" + output
1583         d = self.GET(url)
1584         def _got(res):
1585             if output and output.lower() == "json":
1586                 return simplejson.loads(res)
1587             return res
1588         d.addCallback(_got)
1589         return d
1590
1591     def test_POST_DIRURL_deepcheck_no_ophandle(self):
1592         d = self.shouldFail2(error.Error,
1593                              "test_POST_DIRURL_deepcheck_no_ophandle",
1594                              "400 Bad Request",
1595                              "slow operation requires ophandle=",
1596                              self.POST, self.public_url, t="start-deep-check")
1597         return d
1598
1599     def test_POST_DIRURL_deepcheck(self):
1600         def _check_redirect(statuscode, target):
1601             self.failUnlessEqual(statuscode, str(http.FOUND))
1602             self.failUnless(target.endswith("/operations/123?t=status"))
1603         d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
1604                                  self.POST, self.public_url,
1605                                  t="start-deep-check", ophandle="123")
1606         d.addCallback(self.wait_for_operation, "123")
1607         def _check_json(data):
1608             self.failUnlessEqual(data["finished"], True)
1609             self.failUnlessEqual(data["count-objects-checked"], 8)
1610             self.failUnlessEqual(data["count-objects-healthy"], 8)
1611         d.addCallback(_check_json)
1612         d.addCallback(self.get_operation_results, "123", "html")
1613         def _check_html(res):
1614             self.failUnless("Objects Checked: <span>8</span>" in res)
1615             self.failUnless("Objects Healthy: <span>8</span>" in res)
1616         d.addCallback(_check_html)
1617         return d
1618
1619     def test_POST_DIRURL_deepcheck_and_repair(self):
1620         d = self.POST(self.public_url, t="start-deep-check", repair="true",
1621                       ophandle="124", output="json", followRedirect=True)
1622         d.addCallback(self.wait_for_operation, "124")
1623         def _check_json(data):
1624             self.failUnlessEqual(data["finished"], True)
1625             self.failUnlessEqual(data["count-objects-checked"], 8)
1626             self.failUnlessEqual(data["count-objects-healthy-pre-repair"], 8)
1627             self.failUnlessEqual(data["count-objects-unhealthy-pre-repair"], 0)
1628             self.failUnlessEqual(data["count-corrupt-shares-pre-repair"], 0)
1629             self.failUnlessEqual(data["count-repairs-attempted"], 0)
1630             self.failUnlessEqual(data["count-repairs-successful"], 0)
1631             self.failUnlessEqual(data["count-repairs-unsuccessful"], 0)
1632             self.failUnlessEqual(data["count-objects-healthy-post-repair"], 8)
1633             self.failUnlessEqual(data["count-objects-unhealthy-post-repair"], 0)
1634             self.failUnlessEqual(data["count-corrupt-shares-post-repair"], 0)
1635         d.addCallback(_check_json)
1636         d.addCallback(self.get_operation_results, "124", "html")
1637         def _check_html(res):
1638             self.failUnless("Objects Checked: <span>8</span>" in res)
1639
1640             self.failUnless("Objects Healthy (before repair): <span>8</span>" in res)
1641             self.failUnless("Objects Unhealthy (before repair): <span>0</span>" in res)
1642             self.failUnless("Corrupt Shares (before repair): <span>0</span>" in res)
1643
1644             self.failUnless("Repairs Attempted: <span>0</span>" in res)
1645             self.failUnless("Repairs Successful: <span>0</span>" in res)
1646             self.failUnless("Repairs Unsuccessful: <span>0</span>" in res)
1647
1648             self.failUnless("Objects Healthy (after repair): <span>8</span>" in res)
1649             self.failUnless("Objects Unhealthy (after repair): <span>0</span>" in res)
1650             self.failUnless("Corrupt Shares (after repair): <span>0</span>" in res)
1651         d.addCallback(_check_html)
1652         return d
1653
1654     def test_POST_FILEURL_bad_t(self):
1655         d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
1656                              "POST to file: bad t=bogus",
1657                              self.POST, self.public_url + "/foo/bar.txt",
1658                              t="bogus")
1659         return d
1660
1661     def test_POST_mkdir(self): # return value?
1662         d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
1663         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1664         d.addCallback(self.failUnlessNodeKeysAre, [])
1665         return d
1666
1667     def test_POST_mkdir_2(self):
1668         d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
1669         d.addCallback(lambda res:
1670                       self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1671         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1672         d.addCallback(self.failUnlessNodeKeysAre, [])
1673         return d
1674
1675     def test_POST_mkdirs_2(self):
1676         d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
1677         d.addCallback(lambda res:
1678                       self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
1679         d.addCallback(lambda res: self._foo_node.get(u"bardir"))
1680         d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
1681         d.addCallback(self.failUnlessNodeKeysAre, [])
1682         return d
1683
1684     def test_POST_mkdir_no_parentdir_noredirect(self):
1685         d = self.POST("/uri?t=mkdir")
1686         def _after_mkdir(res):
1687             uri.NewDirectoryURI.init_from_string(res)
1688         d.addCallback(_after_mkdir)
1689         return d
1690
1691     def test_POST_mkdir_no_parentdir_redirect(self):
1692         d = self.POST("/uri?t=mkdir&redirect_to_result=true")
1693         d.addBoth(self.shouldRedirect, None, statuscode='303')
1694         def _check_target(target):
1695             target = urllib.unquote(target)
1696             self.failUnless(target.startswith("uri/URI:DIR2:"), target)
1697         d.addCallback(_check_target)
1698         return d
1699
1700     def test_POST_noparent_bad(self):
1701         d = self.shouldHTTPError2("POST /uri?t=bogus", 400, "Bad Request",
1702                                   "/uri accepts only PUT, PUT?t=mkdir, "
1703                                   "POST?t=upload, and POST?t=mkdir",
1704                                   self.POST, "/uri?t=bogus")
1705         return d
1706
1707     def test_welcome_page_mkdir_button(self):
1708         # Fetch the welcome page.
1709         d = self.GET("/")
1710         def _after_get_welcome_page(res):
1711             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)
1712             mo = MKDIR_BUTTON_RE.search(res)
1713             formaction = mo.group(1)
1714             formt = mo.group(2)
1715             formaname = mo.group(3)
1716             formavalue = mo.group(4)
1717             return (formaction, formt, formaname, formavalue)
1718         d.addCallback(_after_get_welcome_page)
1719         def _after_parse_form(res):
1720             (formaction, formt, formaname, formavalue) = res
1721             return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
1722         d.addCallback(_after_parse_form)
1723         d.addBoth(self.shouldRedirect, None, statuscode='303')
1724         return d
1725
1726     def test_POST_mkdir_replace(self): # return value?
1727         d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
1728         d.addCallback(lambda res: self._foo_node.get(u"sub"))
1729         d.addCallback(self.failUnlessNodeKeysAre, [])
1730         return d
1731
1732     def test_POST_mkdir_no_replace_queryarg(self): # return value?
1733         d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
1734         d.addBoth(self.shouldFail, error.Error,
1735                   "POST_mkdir_no_replace_queryarg",
1736                   "409 Conflict",
1737                   "There was already a child by that name, and you asked me "
1738                   "to not replace it")
1739         d.addCallback(lambda res: self._foo_node.get(u"sub"))
1740         d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1741         return d
1742
1743     def test_POST_mkdir_no_replace_field(self): # return value?
1744         d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
1745                       replace="false")
1746         d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
1747                   "409 Conflict",
1748                   "There was already a child by that name, and you asked me "
1749                   "to not replace it")
1750         d.addCallback(lambda res: self._foo_node.get(u"sub"))
1751         d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1752         return d
1753
1754     def test_POST_mkdir_whendone_field(self):
1755         d = self.POST(self.public_url + "/foo",
1756                       t="mkdir", name="newdir", when_done="/THERE")
1757         d.addBoth(self.shouldRedirect, "/THERE")
1758         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1759         d.addCallback(self.failUnlessNodeKeysAre, [])
1760         return d
1761
1762     def test_POST_mkdir_whendone_queryarg(self):
1763         d = self.POST(self.public_url + "/foo?when_done=/THERE",
1764                       t="mkdir", name="newdir")
1765         d.addBoth(self.shouldRedirect, "/THERE")
1766         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1767         d.addCallback(self.failUnlessNodeKeysAre, [])
1768         return d
1769
1770     def test_POST_bad_t(self):
1771         d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
1772                              "POST to a directory with bad t=BOGUS",
1773                              self.POST, self.public_url + "/foo", t="BOGUS")
1774         return d
1775
1776     def test_POST_set_children(self):
1777         contents9, n9, newuri9 = self.makefile(9)
1778         contents10, n10, newuri10 = self.makefile(10)
1779         contents11, n11, newuri11 = self.makefile(11)
1780
1781         reqbody = """{
1782                      "atomic_added_1": [ "filenode", { "rw_uri": "%s",
1783                                                 "size": 0,
1784                                                 "metadata": {
1785                                                   "ctime": 1002777696.7564139,
1786                                                   "mtime": 1002777696.7564139
1787                                                  }
1788                                                } ],
1789                      "atomic_added_2": [ "filenode", { "rw_uri": "%s",
1790                                                 "size": 1,
1791                                                 "metadata": {
1792                                                   "ctime": 1002777696.7564139,
1793                                                   "mtime": 1002777696.7564139
1794                                                  }
1795                                                } ],
1796                      "atomic_added_3": [ "filenode", { "rw_uri": "%s",
1797                                                 "size": 2,
1798                                                 "metadata": {
1799                                                   "ctime": 1002777696.7564139,
1800                                                   "mtime": 1002777696.7564139
1801                                                  }
1802                                                } ]
1803                     }""" % (newuri9, newuri10, newuri11)
1804
1805         url = self.webish_url + self.public_url + "/foo" + "?t=set_children"
1806
1807         d = client.getPage(url, method="POST", postdata=reqbody)
1808         def _then(res):
1809             self.failUnlessURIMatchesChild(newuri9, self._foo_node, u"atomic_added_1")
1810             self.failUnlessURIMatchesChild(newuri10, self._foo_node, u"atomic_added_2")
1811             self.failUnlessURIMatchesChild(newuri11, self._foo_node, u"atomic_added_3")
1812
1813         d.addCallback(_then)
1814         d.addErrback(self.dump_error)
1815         return d
1816
1817     def test_POST_put_uri(self):
1818         contents, n, newuri = self.makefile(8)
1819         d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
1820         d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
1821         d.addCallback(lambda res:
1822                       self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1823                                                       contents))
1824         return d
1825
1826     def test_POST_put_uri_replace(self):
1827         contents, n, newuri = self.makefile(8)
1828         d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
1829         d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
1830         d.addCallback(lambda res:
1831                       self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1832                                                       contents))
1833         return d
1834
1835     def test_POST_put_uri_no_replace_queryarg(self):
1836         contents, n, newuri = self.makefile(8)
1837         d = self.POST(self.public_url + "/foo?replace=false", t="uri",
1838                       name="bar.txt", uri=newuri)
1839         d.addBoth(self.shouldFail, error.Error,
1840                   "POST_put_uri_no_replace_queryarg",
1841                   "409 Conflict",
1842                   "There was already a child by that name, and you asked me "
1843                   "to not replace it")
1844         d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1845         d.addCallback(self.failUnlessIsBarDotTxt)
1846         return d
1847
1848     def test_POST_put_uri_no_replace_field(self):
1849         contents, n, newuri = self.makefile(8)
1850         d = self.POST(self.public_url + "/foo", t="uri", replace="false",
1851                       name="bar.txt", uri=newuri)
1852         d.addBoth(self.shouldFail, error.Error,
1853                   "POST_put_uri_no_replace_field",
1854                   "409 Conflict",
1855                   "There was already a child by that name, and you asked me "
1856                   "to not replace it")
1857         d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1858         d.addCallback(self.failUnlessIsBarDotTxt)
1859         return d
1860
1861     def test_POST_delete(self):
1862         d = self.POST(self.public_url + "/foo", t="delete", name="bar.txt")
1863         d.addCallback(lambda res: self._foo_node.list())
1864         def _check(children):
1865             self.failIf(u"bar.txt" in children)
1866         d.addCallback(_check)
1867         return d
1868
1869     def test_POST_rename_file(self):
1870         d = self.POST(self.public_url + "/foo", t="rename",
1871                       from_name="bar.txt", to_name='wibble.txt')
1872         d.addCallback(lambda res:
1873                       self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1874         d.addCallback(lambda res:
1875                       self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
1876         d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
1877         d.addCallback(self.failUnlessIsBarDotTxt)
1878         d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
1879         d.addCallback(self.failUnlessIsBarJSON)
1880         return d
1881
1882     def test_POST_rename_file_replace(self):
1883         # rename a file and replace a directory with it
1884         d = self.POST(self.public_url + "/foo", t="rename",
1885                       from_name="bar.txt", to_name='empty')
1886         d.addCallback(lambda res:
1887                       self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1888         d.addCallback(lambda res:
1889                       self.failUnlessNodeHasChild(self._foo_node, u"empty"))
1890         d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
1891         d.addCallback(self.failUnlessIsBarDotTxt)
1892         d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
1893         d.addCallback(self.failUnlessIsBarJSON)
1894         return d
1895
1896     def test_POST_rename_file_no_replace_queryarg(self):
1897         # rename a file and replace a directory with it
1898         d = self.POST(self.public_url + "/foo?replace=false", t="rename",
1899                       from_name="bar.txt", to_name='empty')
1900         d.addBoth(self.shouldFail, error.Error,
1901                   "POST_rename_file_no_replace_queryarg",
1902                   "409 Conflict",
1903                   "There was already a child by that name, and you asked me "
1904                   "to not replace it")
1905         d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
1906         d.addCallback(self.failUnlessIsEmptyJSON)
1907         return d
1908
1909     def test_POST_rename_file_no_replace_field(self):
1910         # rename a file and replace a directory with it
1911         d = self.POST(self.public_url + "/foo", t="rename", replace="false",
1912                       from_name="bar.txt", to_name='empty')
1913         d.addBoth(self.shouldFail, error.Error,
1914                   "POST_rename_file_no_replace_field",
1915                   "409 Conflict",
1916                   "There was already a child by that name, and you asked me "
1917                   "to not replace it")
1918         d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
1919         d.addCallback(self.failUnlessIsEmptyJSON)
1920         return d
1921
1922     def failUnlessIsEmptyJSON(self, res):
1923         data = simplejson.loads(res)
1924         self.failUnlessEqual(data[0], "dirnode", data)
1925         self.failUnlessEqual(len(data[1]["children"]), 0)
1926
1927     def test_POST_rename_file_slash_fail(self):
1928         d = self.POST(self.public_url + "/foo", t="rename",
1929                       from_name="bar.txt", to_name='kirk/spock.txt')
1930         d.addBoth(self.shouldFail, error.Error,
1931                   "test_POST_rename_file_slash_fail",
1932                   "400 Bad Request",
1933                   "to_name= may not contain a slash",
1934                   )
1935         d.addCallback(lambda res:
1936                       self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
1937         return d
1938
1939     def test_POST_rename_dir(self):
1940         d = self.POST(self.public_url, t="rename",
1941                       from_name="foo", to_name='plunk')
1942         d.addCallback(lambda res:
1943                       self.failIfNodeHasChild(self.public_root, u"foo"))
1944         d.addCallback(lambda res:
1945                       self.failUnlessNodeHasChild(self.public_root, u"plunk"))
1946         d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
1947         d.addCallback(self.failUnlessIsFooJSON)
1948         return d
1949
1950     def shouldRedirect(self, res, target=None, statuscode=None, which=""):
1951         """ If target is not None then the redirection has to go to target.  If
1952         statuscode is not None then the redirection has to be accomplished with
1953         that HTTP status code."""
1954         if not isinstance(res, failure.Failure):
1955             to_where = (target is None) and "somewhere" or ("to " + target)
1956             self.fail("%s: we were expecting to get redirected %s, not get an"
1957                       " actual page: %s" % (which, to_where, res))
1958         res.trap(error.PageRedirect)
1959         if statuscode is not None:
1960             self.failUnlessEqual(res.value.status, statuscode,
1961                                  "%s: not a redirect" % which)
1962         if target is not None:
1963             # the PageRedirect does not seem to capture the uri= query arg
1964             # properly, so we can't check for it.
1965             realtarget = self.webish_url + target
1966             self.failUnlessEqual(res.value.location, realtarget,
1967                                  "%s: wrong target" % which)
1968         return res.value.location
1969
1970     def test_GET_URI_form(self):
1971         base = "/uri?uri=%s" % self._bar_txt_uri
1972         # this is supposed to give us a redirect to /uri/$URI, plus arguments
1973         targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
1974         d = self.GET(base)
1975         d.addBoth(self.shouldRedirect, targetbase)
1976         d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
1977         d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
1978         d.addCallback(lambda res: self.GET(base+"&t=json"))
1979         d.addBoth(self.shouldRedirect, targetbase+"?t=json")
1980         d.addCallback(self.log, "about to get file by uri")
1981         d.addCallback(lambda res: self.GET(base, followRedirect=True))
1982         d.addCallback(self.failUnlessIsBarDotTxt)
1983         d.addCallback(self.log, "got file by uri, about to get dir by uri")
1984         d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
1985                                            followRedirect=True))
1986         d.addCallback(self.failUnlessIsFooJSON)
1987         d.addCallback(self.log, "got dir by uri")
1988
1989         return d
1990
1991     def test_GET_URI_form_bad(self):
1992         d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
1993                              "400 Bad Request", "GET /uri requires uri=",
1994                              self.GET, "/uri")
1995         return d
1996
1997     def test_GET_rename_form(self):
1998         d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
1999                      followRedirect=True)
2000         def _check(res):
2001             self.failUnless('name="when_done" value="."' in res, res)
2002             self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
2003         d.addCallback(_check)
2004         return d
2005
2006     def log(self, res, msg):
2007         #print "MSG: %s  RES: %s" % (msg, res)
2008         log.msg(msg)
2009         return res
2010
2011     def test_GET_URI_URL(self):
2012         base = "/uri/%s" % self._bar_txt_uri
2013         d = self.GET(base)
2014         d.addCallback(self.failUnlessIsBarDotTxt)
2015         d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
2016         d.addCallback(self.failUnlessIsBarDotTxt)
2017         d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
2018         d.addCallback(self.failUnlessIsBarDotTxt)
2019         return d
2020
2021     def test_GET_URI_URL_dir(self):
2022         base = "/uri/%s?t=json" % self._foo_uri
2023         d = self.GET(base)
2024         d.addCallback(self.failUnlessIsFooJSON)
2025         return d
2026
2027     def test_GET_URI_URL_missing(self):
2028         base = "/uri/%s" % self._bad_file_uri
2029         d = self.GET(base)
2030         d.addBoth(self.shouldHTTPError, "test_GET_URI_URL_missing",
2031                   http.GONE, response_substring="NotEnoughSharesError")
2032         # TODO: how can we exercise both sides of WebDownloadTarget.fail
2033         # here? we must arrange for a download to fail after target.open()
2034         # has been called, and then inspect the response to see that it is
2035         # shorter than we expected.
2036         return d
2037
2038     def test_PUT_NEWFILEURL_uri(self):
2039         contents, n, new_uri = self.makefile(8)
2040         d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
2041         d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2042         d.addCallback(lambda res:
2043                       self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
2044                                                       contents))
2045         return d
2046
2047     def test_PUT_NEWFILEURL_uri_replace(self):
2048         contents, n, new_uri = self.makefile(8)
2049         d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
2050         d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2051         d.addCallback(lambda res:
2052                       self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
2053                                                       contents))
2054         return d
2055
2056     def test_PUT_NEWFILEURL_uri_no_replace(self):
2057         contents, n, new_uri = self.makefile(8)
2058         d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
2059         d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_uri_no_replace",
2060                   "409 Conflict",
2061                   "There was already a child by that name, and you asked me "
2062                   "to not replace it")
2063         return d
2064
2065     def test_PUT_NEWFILE_URI(self):
2066         file_contents = "New file contents here\n"
2067         d = self.PUT("/uri", file_contents)
2068         def _check(uri):
2069             self.failUnless(uri in FakeCHKFileNode.all_contents)
2070             self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
2071                                  file_contents)
2072             return self.GET("/uri/%s" % uri)
2073         d.addCallback(_check)
2074         def _check2(res):
2075             self.failUnlessEqual(res, file_contents)
2076         d.addCallback(_check2)
2077         return d
2078
2079     def test_PUT_NEWFILE_URI_only_PUT(self):
2080         d = self.PUT("/uri?t=bogus", "")
2081         d.addBoth(self.shouldFail, error.Error,
2082                   "PUT_NEWFILE_URI_only_PUT",
2083                   "400 Bad Request",
2084                   "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
2085         return d
2086
2087     def test_PUT_NEWFILE_URI_mutable(self):
2088         file_contents = "New file contents here\n"
2089         d = self.PUT("/uri?mutable=true", file_contents)
2090         def _check_mutable(uri):
2091             uri = uri.strip()
2092             u = IURI(uri)
2093             self.failUnless(IMutableFileURI.providedBy(u))
2094             self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
2095             n = self.s.create_node_from_uri(uri)
2096             return n.download_best_version()
2097         d.addCallback(_check_mutable)
2098         def _check2_mutable(data):
2099             self.failUnlessEqual(data, file_contents)
2100         d.addCallback(_check2_mutable)
2101         return d
2102
2103         def _check(uri):
2104             self.failUnless(uri in FakeCHKFileNode.all_contents)
2105             self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
2106                                  file_contents)
2107             return self.GET("/uri/%s" % uri)
2108         d.addCallback(_check)
2109         def _check2(res):
2110             self.failUnlessEqual(res, file_contents)
2111         d.addCallback(_check2)
2112         return d
2113
2114     def test_PUT_mkdir(self):
2115         d = self.PUT("/uri?t=mkdir", "")
2116         def _check(uri):
2117             n = self.s.create_node_from_uri(uri.strip())
2118             d2 = self.failUnlessNodeKeysAre(n, [])
2119             d2.addCallback(lambda res:
2120                            self.GET("/uri/%s?t=json" % uri))
2121             return d2
2122         d.addCallback(_check)
2123         d.addCallback(self.failUnlessIsEmptyJSON)
2124         return d
2125
2126     def test_POST_check(self):
2127         d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
2128         def _done(res):
2129             # this returns a string form of the results, which are probably
2130             # None since we're using fake filenodes.
2131             # TODO: verify that the check actually happened, by changing
2132             # FakeCHKFileNode to count how many times .check() has been
2133             # called.
2134             pass
2135         d.addCallback(_done)
2136         return d
2137
2138     def test_bad_method(self):
2139         url = self.webish_url + self.public_url + "/foo/bar.txt"
2140         d = self.shouldHTTPError2("test_bad_method",
2141                                   501, "Not Implemented",
2142                                   "I don't know how to treat a BOGUS request.",
2143                                   client.getPage, url, method="BOGUS")
2144         return d
2145
2146     def test_short_url(self):
2147         url = self.webish_url + "/uri"
2148         d = self.shouldHTTPError2("test_short_url", 501, "Not Implemented",
2149                                   "I don't know how to treat a DELETE request.",
2150                                   client.getPage, url, method="DELETE")
2151         return d
2152
2153     def test_bad_ophandle(self):
2154         url = self.webish_url + "/operations/bogus?t=status"
2155         d = self.shouldHTTPError2("test_bad_ophandle", 400, "400 Bad Request",
2156                                   "unknown/expired handle 'bogus'",
2157                                   client.getPage, url)
2158         return d
2159
2160     def test_incident(self):
2161         d = self.POST("/report_incident", details="eek")
2162         def _done(res):
2163             self.failUnless("Thank you for your report!" in res, res)
2164         d.addCallback(_done)
2165         return d
2166
2167
2168 class Util(unittest.TestCase):
2169     def test_abbreviate_time(self):
2170         self.failUnlessEqual(common.abbreviate_time(None), "")
2171         self.failUnlessEqual(common.abbreviate_time(1.234), "1.23s")
2172         self.failUnlessEqual(common.abbreviate_time(0.123), "123ms")
2173         self.failUnlessEqual(common.abbreviate_time(0.00123), "1.2ms")
2174         self.failUnlessEqual(common.abbreviate_time(0.000123), "123us")
2175
2176     def test_abbreviate_rate(self):
2177         self.failUnlessEqual(common.abbreviate_rate(None), "")
2178         self.failUnlessEqual(common.abbreviate_rate(1234000), "1.23MBps")
2179         self.failUnlessEqual(common.abbreviate_rate(12340), "12.3kBps")
2180         self.failUnlessEqual(common.abbreviate_rate(123), "123Bps")
2181
2182     def test_abbreviate_size(self):
2183         self.failUnlessEqual(common.abbreviate_size(None), "")
2184         self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
2185         self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
2186         self.failUnlessEqual(common.abbreviate_size(1230), "1.2kB")
2187         self.failUnlessEqual(common.abbreviate_size(123), "123B")
2188