Ticket #833: test-diff.txt

File test-diff.txt, 169.3 KB (added by davidsarah, at 2010-01-23T13:05:21Z)

Diff for tests

Line 
1diff -rN -u old-tahoe/src/allmydata/test/common.py new-tahoe/src/allmydata/test/common.py
2--- old-tahoe/src/allmydata/test/common.py      2010-01-23 12:59:04.843000000 +0000
3+++ new-tahoe/src/allmydata/test/common.py      2010-01-23 12:59:05.306000000 +0000
4@@ -51,6 +51,8 @@
5 
6     def get_uri(self):
7         return self.my_uri.to_string()
8+    def get_write_uri(self):
9+        return None
10     def get_readonly_uri(self):
11         return self.my_uri.to_string()
12     def get_cap(self):
13@@ -103,6 +105,12 @@
14         return False
15     def is_readonly(self):
16         return True
17+    def is_unknown(self):
18+        return False
19+    def is_allowed_in_immutable_directory(self):
20+        return True
21+    def raise_error(self):
22+        pass
23 
24     def get_size(self):
25         try:
26@@ -190,6 +198,10 @@
27         return self.my_uri.get_readonly()
28     def get_uri(self):
29         return self.my_uri.to_string()
30+    def get_write_uri(self):
31+        if self.is_readonly():
32+            return None
33+        return self.my_uri.to_string()
34     def get_readonly(self):
35         return self.my_uri.get_readonly()
36     def get_readonly_uri(self):
37@@ -200,6 +212,12 @@
38         return self.my_uri.is_readonly()
39     def is_mutable(self):
40         return self.my_uri.is_mutable()
41+    def is_unknown(self):
42+        return False
43+    def is_allowed_in_immutable_directory(self):
44+        return not self.my_uri.is_mutable()
45+    def raise_error(self):
46+        pass
47     def get_writekey(self):
48         return "\x00"*16
49     def get_size(self):
50diff -rN -u old-tahoe/src/allmydata/test/test_client.py new-tahoe/src/allmydata/test/test_client.py
51--- old-tahoe/src/allmydata/test/test_client.py 2010-01-23 12:59:04.959000000 +0000
52+++ new-tahoe/src/allmydata/test/test_client.py 2010-01-23 12:59:05.413000000 +0000
53@@ -288,11 +288,14 @@
54         self.failUnless(n.is_readonly())
55         self.failUnless(n.is_mutable())
56 
57-        future = "x-tahoe-crazy://future_cap_format."
58-        n = c.create_node_from_uri(future)
59+        unknown_rw = "lafs://from_the_future"
60+        unknown_ro = "lafs://readonly_from_the_future"
61+        n = c.create_node_from_uri(unknown_rw, unknown_ro)
62         self.failUnless(IFilesystemNode.providedBy(n))
63         self.failIf(IFileNode.providedBy(n))
64         self.failIf(IImmutableFileNode.providedBy(n))
65         self.failIf(IMutableFileNode.providedBy(n))
66         self.failIf(IDirectoryNode.providedBy(n))
67-        self.failUnlessEqual(n.get_uri(), future)
68+        self.failUnless(n.is_unknown())
69+        self.failUnlessEqual(n.get_uri(), unknown_rw)
70+        self.failUnlessEqual(n.get_readonly_uri(), "ro." + unknown_ro)
71diff -rN -u old-tahoe/src/allmydata/test/test_dirnode.py new-tahoe/src/allmydata/test/test_dirnode.py
72--- old-tahoe/src/allmydata/test/test_dirnode.py        2010-01-23 12:59:04.984000000 +0000
73+++ new-tahoe/src/allmydata/test/test_dirnode.py        2010-01-23 12:59:05.438000000 +0000
74@@ -7,8 +7,8 @@
75 from allmydata.client import Client
76 from allmydata.immutable import upload
77 from allmydata.interfaces import IImmutableFileNode, IMutableFileNode, \
78-     ExistingChildError, NoSuchChildError, NotDeepImmutableError, \
79-     IDeepCheckResults, IDeepCheckAndRepairResults, CannotPackUnknownNodeError
80+     ExistingChildError, NoSuchChildError, MustBeDeepImmutableError, \
81+     IDeepCheckResults, IDeepCheckAndRepairResults, MustNotBeUnknownRWError
82 from allmydata.mutable.filenode import MutableFileNode
83 from allmydata.mutable.common import UncoordinatedWriteError
84 from allmydata.util import hashutil, base32
85@@ -32,6 +32,11 @@
86         d = c.create_dirnode()
87         def _done(res):
88             self.failUnless(isinstance(res, dirnode.DirectoryNode))
89+            self.failUnless(res.is_mutable())
90+            self.failIf(res.is_readonly())
91+            self.failIf(res.is_unknown())
92+            self.failIf(res.is_allowed_in_immutable_directory())
93+            res.raise_error()
94             rep = str(res)
95             self.failUnless("RW-MUT" in rep)
96         d.addCallback(_done)
97@@ -44,36 +49,74 @@
98         nm = c.nodemaker
99         setup_py_uri = "URI:CHK:n7r3m6wmomelk4sep3kw5cvduq:os7ijw5c3maek7pg65e5254k2fzjflavtpejjyhshpsxuqzhcwwq:3:20:14861"
100         one_uri = "URI:LIT:n5xgk" # LIT for "one"
101+        mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
102+        mut_read_uri = "URI:SSK-RO:jf6wkflosyvntwxqcdo7a54jvm:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
103+        future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
104+        future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future."
105         kids = {u"one": (nm.create_from_cap(one_uri), {}),
106                 u"two": (nm.create_from_cap(setup_py_uri),
107                          {"metakey": "metavalue"}),
108+                u"mut": (nm.create_from_cap(mut_write_uri, mut_read_uri), {}),
109+                u"fut": (nm.create_from_cap(future_write_uri, future_read_uri), {}),
110+                u"fro": (nm.create_from_cap(None, future_read_uri), {}),
111                 }
112         d = c.create_dirnode(kids)
113+       
114         def _created(dn):
115             self.failUnless(isinstance(dn, dirnode.DirectoryNode))
116+            self.failUnless(dn.is_mutable())
117+            self.failIf(dn.is_readonly())
118+            self.failIf(dn.is_unknown())
119+            self.failIf(dn.is_allowed_in_immutable_directory())
120+            dn.raise_error()
121             rep = str(dn)
122             self.failUnless("RW-MUT" in rep)
123             return dn.list()
124         d.addCallback(_created)
125+       
126         def _check_kids(children):
127-            self.failUnlessEqual(sorted(children.keys()), [u"one", u"two"])
128+            self.failUnlessEqual(sorted(children.keys()),
129+                                 [u"fro", u"fut", u"mut", u"one", u"two"])
130             one_node, one_metadata = children[u"one"]
131             two_node, two_metadata = children[u"two"]
132+            mut_node, mut_metadata = children[u"mut"]
133+            fut_node, fut_metadata = children[u"fut"]
134+            fro_node, fro_metadata = children[u"fro"]
135+           
136             self.failUnlessEqual(one_node.get_size(), 3)
137-            self.failUnlessEqual(two_node.get_size(), 14861)
138+            self.failUnlessEqual(one_node.get_uri(), one_uri)
139+            self.failUnlessEqual(one_node.get_readonly_uri(), one_uri)
140             self.failUnless(isinstance(one_metadata, dict), one_metadata)
141+           
142+            self.failUnlessEqual(two_node.get_size(), 14861)
143+            self.failUnlessEqual(two_node.get_uri(), setup_py_uri)
144+            self.failUnlessEqual(two_node.get_readonly_uri(), setup_py_uri)
145             self.failUnlessEqual(two_metadata["metakey"], "metavalue")
146+           
147+            self.failUnlessEqual(mut_node.get_uri(), mut_write_uri)
148+            self.failUnlessEqual(mut_node.get_readonly_uri(), mut_read_uri)
149+            self.failUnless(isinstance(mut_metadata, dict), mut_metadata)
150+           
151+            self.failUnless(fut_node.is_unknown())
152+            self.failUnlessEqual(fut_node.get_uri(), future_write_uri)
153+            self.failUnlessEqual(fut_node.get_readonly_uri(), "ro." + future_read_uri)
154+            self.failUnless(isinstance(fut_metadata, dict), fut_metadata)
155+           
156+            self.failUnless(fro_node.is_unknown())
157+            self.failUnlessEqual(fro_node.get_uri(), "ro." + future_read_uri)
158+            self.failUnlessEqual(fut_node.get_readonly_uri(), "ro." + future_read_uri)
159+            self.failUnless(isinstance(fro_metadata, dict), fro_metadata)
160         d.addCallback(_check_kids)
161+
162         d.addCallback(lambda ign: nm.create_new_mutable_directory(kids))
163         d.addCallback(lambda dn: dn.list())
164         d.addCallback(_check_kids)
165-        future_writecap = "x-tahoe-crazy://I_am_from_the_future."
166-        future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future."
167-        future_node = UnknownNode(future_writecap, future_readcap)
168-        bad_kids1 = {u"one": (future_node, {})}
169+
170+        bad_future_node = UnknownNode(future_write_uri, None)
171+        bad_kids1 = {u"one": (bad_future_node, {})}
172         d.addCallback(lambda ign:
173-                      self.shouldFail(AssertionError, "bad_kids1",
174-                                      "does not accept UnknownNode",
175+                      self.shouldFail(MustNotBeUnknownRWError, "bad_kids1",
176+                                      "cannot attach unknown",
177                                       nm.create_new_mutable_directory,
178                                       bad_kids1))
179         bad_kids2 = {u"one": (nm.create_from_cap(one_uri), None)}
180@@ -91,17 +134,24 @@
181         nm = c.nodemaker
182         setup_py_uri = "URI:CHK:n7r3m6wmomelk4sep3kw5cvduq:os7ijw5c3maek7pg65e5254k2fzjflavtpejjyhshpsxuqzhcwwq:3:20:14861"
183         one_uri = "URI:LIT:n5xgk" # LIT for "one"
184-        mut_readcap = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
185-        mut_writecap = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
186+        mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
187+        mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
188+        future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
189+        future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future."
190         kids = {u"one": (nm.create_from_cap(one_uri), {}),
191                 u"two": (nm.create_from_cap(setup_py_uri),
192                          {"metakey": "metavalue"}),
193+                u"fut": (nm.create_from_cap(None, future_read_uri), {}),
194                 }
195         d = c.create_immutable_dirnode(kids)
196+       
197         def _created(dn):
198             self.failUnless(isinstance(dn, dirnode.DirectoryNode))
199             self.failIf(dn.is_mutable())
200             self.failUnless(dn.is_readonly())
201+            self.failIf(dn.is_unknown())
202+            self.failUnless(dn.is_allowed_in_immutable_directory())
203+            dn.raise_error()
204             rep = str(dn)
205             self.failUnless("RO-IMM" in rep)
206             cap = dn.get_cap()
207@@ -109,50 +159,73 @@
208             self.cap = cap
209             return dn.list()
210         d.addCallback(_created)
211+       
212         def _check_kids(children):
213-            self.failUnlessEqual(sorted(children.keys()), [u"one", u"two"])
214+            self.failUnlessEqual(sorted(children.keys()), [u"fut", u"one", u"two"])
215             one_node, one_metadata = children[u"one"]
216             two_node, two_metadata = children[u"two"]
217+            fut_node, fut_metadata = children[u"fut"]
218+
219             self.failUnlessEqual(one_node.get_size(), 3)
220-            self.failUnlessEqual(two_node.get_size(), 14861)
221+            self.failUnlessEqual(one_node.get_uri(), one_uri)
222+            self.failUnlessEqual(one_node.get_readonly_uri(), one_uri)
223             self.failUnless(isinstance(one_metadata, dict), one_metadata)
224+
225+            self.failUnlessEqual(two_node.get_size(), 14861)
226+            self.failUnlessEqual(two_node.get_uri(), setup_py_uri)
227+            self.failUnlessEqual(two_node.get_readonly_uri(), setup_py_uri)
228             self.failUnlessEqual(two_metadata["metakey"], "metavalue")
229+
230+            self.failUnless(fut_node.is_unknown())
231+            self.failUnlessEqual(fut_node.get_uri(), "imm." + future_read_uri)
232+            self.failUnlessEqual(fut_node.get_readonly_uri(), "imm." + future_read_uri)
233+            self.failUnless(isinstance(fut_metadata, dict), fut_metadata)
234         d.addCallback(_check_kids)
235+       
236         d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
237         d.addCallback(lambda dn: dn.list())
238         d.addCallback(_check_kids)
239-        future_writecap = "x-tahoe-crazy://I_am_from_the_future."
240-        future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future."
241-        future_node = UnknownNode(future_writecap, future_readcap)
242-        bad_kids1 = {u"one": (future_node, {})}
243+
244+        bad_future_node1 = UnknownNode(future_write_uri, None)
245+        bad_kids1 = {u"one": (bad_future_node1, {})}
246         d.addCallback(lambda ign:
247-                      self.shouldFail(AssertionError, "bad_kids1",
248-                                      "does not accept UnknownNode",
249+                      self.shouldFail(MustNotBeUnknownRWError, "bad_kids1",
250+                                      "cannot attach unknown",
251                                       c.create_immutable_dirnode,
252                                       bad_kids1))
253-        bad_kids2 = {u"one": (nm.create_from_cap(one_uri), None)}
254+        bad_future_node2 = UnknownNode(future_write_uri, future_read_uri)
255+        bad_kids2 = {u"one": (bad_future_node2, {})}
256         d.addCallback(lambda ign:
257-                      self.shouldFail(AssertionError, "bad_kids2",
258-                                      "requires metadata to be a dict",
259+                      self.shouldFail(MustBeDeepImmutableError, "bad_kids2",
260+                                      "is not immutable",
261                                       c.create_immutable_dirnode,
262                                       bad_kids2))
263-        bad_kids3 = {u"one": (nm.create_from_cap(mut_writecap), {})}
264+        bad_kids3 = {u"one": (nm.create_from_cap(one_uri), None)}
265         d.addCallback(lambda ign:
266-                      self.shouldFail(NotDeepImmutableError, "bad_kids3",
267-                                      "is not immutable",
268+                      self.shouldFail(AssertionError, "bad_kids3",
269+                                      "requires metadata to be a dict",
270                                       c.create_immutable_dirnode,
271                                       bad_kids3))
272-        bad_kids4 = {u"one": (nm.create_from_cap(mut_readcap), {})}
273+        bad_kids4 = {u"one": (nm.create_from_cap(mut_write_uri), {})}
274         d.addCallback(lambda ign:
275-                      self.shouldFail(NotDeepImmutableError, "bad_kids4",
276+                      self.shouldFail(MustBeDeepImmutableError, "bad_kids4",
277                                       "is not immutable",
278                                       c.create_immutable_dirnode,
279                                       bad_kids4))
280+        bad_kids5 = {u"one": (nm.create_from_cap(mut_read_uri), {})}
281+        d.addCallback(lambda ign:
282+                      self.shouldFail(MustBeDeepImmutableError, "bad_kids5",
283+                                      "is not immutable",
284+                                      c.create_immutable_dirnode,
285+                                      bad_kids5))
286         d.addCallback(lambda ign: c.create_immutable_dirnode({}))
287         def _created_empty(dn):
288             self.failUnless(isinstance(dn, dirnode.DirectoryNode))
289             self.failIf(dn.is_mutable())
290             self.failUnless(dn.is_readonly())
291+            self.failIf(dn.is_unknown())
292+            self.failUnless(dn.is_allowed_in_immutable_directory())
293+            dn.raise_error()
294             rep = str(dn)
295             self.failUnless("RO-IMM" in rep)
296             cap = dn.get_cap()
297@@ -168,6 +241,9 @@
298             self.failUnless(isinstance(dn, dirnode.DirectoryNode))
299             self.failIf(dn.is_mutable())
300             self.failUnless(dn.is_readonly())
301+            self.failIf(dn.is_unknown())
302+            self.failUnless(dn.is_allowed_in_immutable_directory())
303+            dn.raise_error()
304             rep = str(dn)
305             self.failUnless("RO-IMM" in rep)
306             cap = dn.get_cap()
307@@ -193,9 +269,9 @@
308             d.addCallback(_check_kids)
309             d.addCallback(lambda ign: n.get(u"subdir"))
310             d.addCallback(lambda sd: self.failIf(sd.is_mutable()))
311-            bad_kids = {u"one": (nm.create_from_cap(mut_writecap), {})}
312+            bad_kids = {u"one": (nm.create_from_cap(mut_write_uri), {})}
313             d.addCallback(lambda ign:
314-                          self.shouldFail(NotDeepImmutableError, "YZ",
315+                          self.shouldFail(MustBeDeepImmutableError, "YZ",
316                                           "is not immutable",
317                                           n.create_subdirectory,
318                                           u"sub2", bad_kids, mutable=False))
319@@ -203,7 +279,6 @@
320         d.addCallback(_made_parent)
321         return d
322 
323-
324     def test_check(self):
325         self.basedir = "dirnode/Dirnode/test_check"
326         self.set_up_grid()
327@@ -337,24 +412,27 @@
328             ro_dn = c.create_node_from_uri(ro_uri)
329             self.failUnless(ro_dn.is_readonly())
330             self.failUnless(ro_dn.is_mutable())
331+            self.failIf(ro_dn.is_unknown())
332+            self.failIf(ro_dn.is_allowed_in_immutable_directory())
333+            ro_dn.raise_error()
334 
335-            self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
336+            self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None,
337                             ro_dn.set_uri, u"newchild", filecap, filecap)
338-            self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
339+            self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None,
340                             ro_dn.set_node, u"newchild", filenode)
341-            self.shouldFail(dirnode.NotMutableError, "set_nodes ro", None,
342+            self.shouldFail(dirnode.NotWriteableError, "set_nodes ro", None,
343                             ro_dn.set_nodes, { u"newchild": (filenode, None) })
344-            self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
345+            self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None,
346                             ro_dn.add_file, u"newchild", uploadable)
347-            self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
348+            self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None,
349                             ro_dn.delete, u"child")
350-            self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
351+            self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None,
352                             ro_dn.create_subdirectory, u"newchild")
353-            self.shouldFail(dirnode.NotMutableError, "set_metadata_for ro", None,
354+            self.shouldFail(dirnode.NotWriteableError, "set_metadata_for ro", None,
355                             ro_dn.set_metadata_for, u"child", {})
356-            self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
357+            self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None,
358                             ro_dn.move_child_to, u"child", rw_dn)
359-            self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
360+            self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None,
361                             rw_dn.move_child_to, u"child", ro_dn)
362             return ro_dn.list()
363         d.addCallback(_ready)
364@@ -901,8 +979,8 @@
365         nodemaker = NodeMaker(None, None, None,
366                               None, None, None,
367                               {"k": 3, "n": 10}, None)
368-        writecap = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
369-        filenode = nodemaker.create_from_cap(writecap)
370+        write_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
371+        filenode = nodemaker.create_from_cap(write_uri)
372         node = dirnode.DirectoryNode(filenode, nodemaker, None)
373         children = node._unpack_contents(known_tree)
374         self._check_children(children)
375@@ -975,23 +1053,23 @@
376         self.failUnlessIn("lit", packed)
377 
378         kids = self._make_kids(nm, ["imm", "lit", "write"])
379-        self.failUnlessRaises(dirnode.MustBeDeepImmutable,
380+        self.failUnlessRaises(dirnode.MustBeDeepImmutableError,
381                               dirnode.pack_children,
382                               fn, kids, deep_immutable=True)
383 
384         # read-only is not enough: all children must be immutable
385         kids = self._make_kids(nm, ["imm", "lit", "read"])
386-        self.failUnlessRaises(dirnode.MustBeDeepImmutable,
387+        self.failUnlessRaises(dirnode.MustBeDeepImmutableError,
388                               dirnode.pack_children,
389                               fn, kids, deep_immutable=True)
390 
391         kids = self._make_kids(nm, ["imm", "lit", "dirwrite"])
392-        self.failUnlessRaises(dirnode.MustBeDeepImmutable,
393+        self.failUnlessRaises(dirnode.MustBeDeepImmutableError,
394                               dirnode.pack_children,
395                               fn, kids, deep_immutable=True)
396 
397         kids = self._make_kids(nm, ["imm", "lit", "dirread"])
398-        self.failUnlessRaises(dirnode.MustBeDeepImmutable,
399+        self.failUnlessRaises(dirnode.MustBeDeepImmutableError,
400                               dirnode.pack_children,
401                               fn, kids, deep_immutable=True)
402 
403@@ -1017,16 +1095,31 @@
404 
405     def get_cap(self):
406         return self.uri
407+
408     def get_uri(self):
409         return self.uri.to_string()
410+
411+    def get_write_uri(self):
412+        return self.uri.to_string()
413+
414     def download_best_version(self):
415         return defer.succeed(self.data)
416+
417     def get_writekey(self):
418         return "writekey"
419+
420     def is_readonly(self):
421         return False
422+
423     def is_mutable(self):
424         return True
425+
426+    def is_unknown(self):
427+        return False
428+
429+    def is_allowed_in_immutable_directory(self):
430+        return False
431+
432     def modify(self, modifier):
433         self.data = modifier(self.data, None, True)
434         return defer.succeed(None)
435@@ -1050,47 +1143,59 @@
436 
437     def test_from_future(self):
438         # create a dirnode that contains unknown URI types, and make sure we
439-        # tolerate them properly. Since dirnodes aren't allowed to add
440-        # unknown node types, we have to be tricky.
441+        # tolerate them properly.
442         d = self.nodemaker.create_new_mutable_directory()
443-        future_writecap = "x-tahoe-crazy://I_am_from_the_future."
444-        future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future."
445-        future_node = UnknownNode(future_writecap, future_readcap)
446+        future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
447+        future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future."
448+        future_node = UnknownNode(future_write_uri, future_read_uri)
449         def _then(n):
450             self._node = n
451             return n.set_node(u"future", future_node)
452         d.addCallback(_then)
453 
454-        # we should be prohibited from adding an unknown URI to a directory,
455-        # since we don't know how to diminish the cap to a readcap (for the
456-        # dirnode's rocap slot), and we don't want to accidentally grant
457-        # write access to a holder of the dirnode's readcap.
458+        # We should be prohibited from adding an unknown URI to a directory
459+        # just in the rw_uri slot, since we don't know how to diminish the cap
460+        # to a readcap (for the ro_uri slot).
461         d.addCallback(lambda ign:
462-             self.shouldFail(CannotPackUnknownNodeError,
463+             self.shouldFail(MustNotBeUnknownRWError,
464                              "copy unknown",
465-                             "cannot pack unknown node as child add",
466+                             "cannot attach unknown rw cap as child",
467                              self._node.set_uri, u"add",
468-                             future_writecap, future_readcap))
469+                             future_write_uri, None))
470+
471+        # However, we should be able to add both rw_uri and ro_uri as a pair of
472+        # unknown URIs.
473+        d.addCallback(lambda ign: self._node.set_uri(u"add-pair",
474+                                                     future_write_uri, future_read_uri))
475+
476         d.addCallback(lambda ign: self._node.list())
477         def _check(children):
478-            self.failUnlessEqual(len(children), 1)
479+            self.failUnlessEqual(len(children), 2)
480             (fn, metadata) = children[u"future"]
481             self.failUnless(isinstance(fn, UnknownNode), fn)
482-            self.failUnlessEqual(fn.get_uri(), future_writecap)
483-            self.failUnlessEqual(fn.get_readonly_uri(), future_readcap)
484-            # but we *should* be allowed to copy this node, because the
485+            self.failUnlessEqual(fn.get_uri(), future_write_uri)
486+            self.failUnlessEqual(fn.get_readonly_uri(), "ro." + future_read_uri)
487+
488+            (fn2, metadata2) = children[u"add-pair"]
489+            self.failUnless(isinstance(fn2, UnknownNode), fn2)
490+            self.failUnlessEqual(fn2.get_uri(), future_write_uri)
491+            self.failUnlessEqual(fn2.get_readonly_uri(), "ro." + future_read_uri)
492+
493+            # we should also be allowed to copy this node, because the
494             # UnknownNode contains all the information that was in the
495             # original directory (readcap and writecap), so we're preserving
496             # everything.
497             return self._node.set_node(u"copy", fn)
498         d.addCallback(_check)
499+
500         d.addCallback(lambda ign: self._node.list())
501         def _check2(children):
502-            self.failUnlessEqual(len(children), 2)
503+            self.failUnlessEqual(len(children), 3)
504             (fn, metadata) = children[u"copy"]
505             self.failUnless(isinstance(fn, UnknownNode), fn)
506-            self.failUnlessEqual(fn.get_uri(), future_writecap)
507-            self.failUnlessEqual(fn.get_readonly_uri(), future_readcap)
508+            self.failUnlessEqual(fn.get_uri(), future_write_uri)
509+            self.failUnlessEqual(fn.get_readonly_uri(), "ro." + future_read_uri)
510+        d.addCallback(_check2)
511         return d
512 
513 class DeepStats(unittest.TestCase):
514diff -rN -u old-tahoe/src/allmydata/test/test_filenode.py new-tahoe/src/allmydata/test/test_filenode.py
515--- old-tahoe/src/allmydata/test/test_filenode.py       2010-01-23 12:59:04.999000000 +0000
516+++ new-tahoe/src/allmydata/test/test_filenode.py       2010-01-23 12:59:05.452000000 +0000
517@@ -41,14 +41,21 @@
518         self.failUnlessEqual(fn1.get_readcap(), u)
519         self.failUnlessEqual(fn1.is_readonly(), True)
520         self.failUnlessEqual(fn1.is_mutable(), False)
521+        self.failUnlessEqual(fn1.is_unknown(), False)
522+        self.failUnlessEqual(fn1.is_allowed_in_immutable_directory(), True)
523+        self.failUnlessEqual(fn1.get_write_uri(), None)
524         self.failUnlessEqual(fn1.get_readonly_uri(), u.to_string())
525         self.failUnlessEqual(fn1.get_size(), 1000)
526         self.failUnlessEqual(fn1.get_storage_index(), u.storage_index)
527+        fn1.raise_error()
528+        fn2.raise_error()
529         d = {}
530         d[fn1] = 1 # exercise __hash__
531         v = fn1.get_verify_cap()
532         self.failUnless(isinstance(v, uri.CHKFileVerifierURI))
533         self.failUnlessEqual(fn1.get_repair_cap(), v)
534+        self.failUnlessEqual(v.is_readonly(), True)
535+        self.failUnlessEqual(v.is_mutable(), False)
536 
537 
538     def test_literal_filenode(self):
539@@ -64,9 +71,14 @@
540         self.failUnlessEqual(fn1.get_readcap(), u)
541         self.failUnlessEqual(fn1.is_readonly(), True)
542         self.failUnlessEqual(fn1.is_mutable(), False)
543+        self.failUnlessEqual(fn1.is_unknown(), False)
544+        self.failUnlessEqual(fn1.is_allowed_in_immutable_directory(), True)
545+        self.failUnlessEqual(fn1.get_write_uri(), None)
546         self.failUnlessEqual(fn1.get_readonly_uri(), u.to_string())
547         self.failUnlessEqual(fn1.get_size(), len(DATA))
548         self.failUnlessEqual(fn1.get_storage_index(), None)
549+        fn1.raise_error()
550+        fn2.raise_error()
551         d = {}
552         d[fn1] = 1 # exercise __hash__
553 
554@@ -99,24 +111,29 @@
555         self.failUnlessEqual(n.get_writekey(), wk)
556         self.failUnlessEqual(n.get_readkey(), rk)
557         self.failUnlessEqual(n.get_storage_index(), si)
558-        # these itmes are populated on first read (or create), so until that
559+        # these items are populated on first read (or create), so until that
560         # happens they'll be None
561         self.failUnlessEqual(n.get_privkey(), None)
562         self.failUnlessEqual(n.get_encprivkey(), None)
563         self.failUnlessEqual(n.get_pubkey(), None)
564 
565         self.failUnlessEqual(n.get_uri(), u.to_string())
566+        self.failUnlessEqual(n.get_write_uri(), u.to_string())
567         self.failUnlessEqual(n.get_readonly_uri(), u.get_readonly().to_string())
568         self.failUnlessEqual(n.get_cap(), u)
569         self.failUnlessEqual(n.get_readcap(), u.get_readonly())
570         self.failUnlessEqual(n.is_mutable(), True)
571         self.failUnlessEqual(n.is_readonly(), False)
572+        self.failUnlessEqual(n.is_unknown(), False)
573+        self.failUnlessEqual(n.is_allowed_in_immutable_directory(), False)
574+        n.raise_error()
575 
576         n2 = MutableFileNode(None, None, client.get_encoding_parameters(),
577                              None).init_from_cap(u)
578         self.failUnlessEqual(n, n2)
579         self.failIfEqual(n, "not even the right type")
580         self.failIfEqual(n, u) # not the right class
581+        n.raise_error()
582         d = {n: "can these be used as dictionary keys?"}
583         d[n2] = "replace the old one"
584         self.failUnlessEqual(len(d), 1)
585@@ -127,12 +144,16 @@
586         self.failUnlessEqual(nro.get_readonly(), nro)
587         self.failUnlessEqual(nro.get_cap(), u.get_readonly())
588         self.failUnlessEqual(nro.get_readcap(), u.get_readonly())
589+        self.failUnlessEqual(nro.is_mutable(), True)
590+        self.failUnlessEqual(nro.is_readonly(), True)
591+        self.failUnlessEqual(nro.is_unknown(), False)
592+        self.failUnlessEqual(nro.is_allowed_in_immutable_directory(), False)
593         nro_u = nro.get_uri()
594         self.failUnlessEqual(nro_u, nro.get_readonly_uri())
595         self.failUnlessEqual(nro_u, u.get_readonly().to_string())
596-        self.failUnlessEqual(nro.is_mutable(), True)
597-        self.failUnlessEqual(nro.is_readonly(), True)
598+        self.failUnlessEqual(nro.get_write_uri(), None)
599         self.failUnlessEqual(nro.get_repair_cap(), None) # RSAmut needs writecap
600+        nro.raise_error()
601 
602         v = n.get_verify_cap()
603         self.failUnless(isinstance(v, uri.SSKVerifierURI))
604diff -rN -u old-tahoe/src/allmydata/test/test_system.py new-tahoe/src/allmydata/test/test_system.py
605--- old-tahoe/src/allmydata/test/test_system.py 2010-01-23 12:59:05.147000000 +0000
606+++ new-tahoe/src/allmydata/test/test_system.py 2010-01-23 12:59:05.618000000 +0000
607@@ -17,7 +17,7 @@
608 from allmydata.interfaces import IDirectoryNode, IFileNode, \
609      NoSuchChildError, NoSharesError
610 from allmydata.monitor import Monitor
611-from allmydata.mutable.common import NotMutableError
612+from allmydata.mutable.common import NotWriteableError
613 from allmydata.mutable import layout as mutable_layout
614 from foolscap.api import DeadReferenceError
615 from twisted.python.failure import Failure
616@@ -890,11 +890,11 @@
617             d1.addCallback(lambda res: dirnode.list())
618             d1.addCallback(self.log, "dirnode.list")
619 
620-            d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mkdir(nope)", None, dirnode.create_subdirectory, u"nope"))
621+            d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "mkdir(nope)", None, dirnode.create_subdirectory, u"nope"))
622 
623             d1.addCallback(self.log, "doing add_file(ro)")
624             ut = upload.Data("I will disappear, unrecorded and unobserved. The tragedy of my demise is made more poignant by its silence, but this beauty is not for you to ever know.", convergence="99i-p1x4-xd4-18yc-ywt-87uu-msu-zo -- completely and totally unguessable string (unless you read this)")
625-            d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "add_file(nope)", None, dirnode.add_file, u"hope", ut))
626+            d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "add_file(nope)", None, dirnode.add_file, u"hope", ut))
627 
628             d1.addCallback(self.log, "doing get(ro)")
629             d1.addCallback(lambda res: dirnode.get(u"mydata992"))
630@@ -902,17 +902,17 @@
631                            self.failUnless(IFileNode.providedBy(filenode)))
632 
633             d1.addCallback(self.log, "doing delete(ro)")
634-            d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "delete(nope)", None, dirnode.delete, u"mydata992"))
635+            d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "delete(nope)", None, dirnode.delete, u"mydata992"))
636 
637-            d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "set_uri(nope)", None, dirnode.set_uri, u"hopeless", self.uri, self.uri))
638+            d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "set_uri(nope)", None, dirnode.set_uri, u"hopeless", self.uri, self.uri))
639 
640             d1.addCallback(lambda res: self.shouldFail2(NoSuchChildError, "get(missing)", "missing", dirnode.get, u"missing"))
641 
642             personal = self._personal_node
643-            d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mv from readonly", None, dirnode.move_child_to, u"mydata992", personal, u"nope"))
644+            d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "mv from readonly", None, dirnode.move_child_to, u"mydata992", personal, u"nope"))
645 
646             d1.addCallback(self.log, "doing move_child_to(ro)2")
647-            d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mv to readonly", None, personal.move_child_to, u"sekrit data", dirnode, u"nope"))
648+            d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "mv to readonly", None, personal.move_child_to, u"sekrit data", dirnode, u"nope"))
649 
650             d1.addCallback(self.log, "finished with _got_s2ro")
651             return d1
652diff -rN -u old-tahoe/src/allmydata/test/test_uri.py new-tahoe/src/allmydata/test/test_uri.py
653--- old-tahoe/src/allmydata/test/test_uri.py    2010-01-23 12:59:05.156000000 +0000
654+++ new-tahoe/src/allmydata/test/test_uri.py    2010-01-23 12:59:05.626000000 +0000
655@@ -3,7 +3,7 @@
656 from allmydata import uri
657 from allmydata.util import hashutil, base32
658 from allmydata.interfaces import IURI, IFileURI, IDirnodeURI, IMutableFileURI, \
659-    IVerifierURI
660+    IVerifierURI, CapConstraintError
661 
662 class Literal(unittest.TestCase):
663     def _help_test(self, data):
664@@ -22,8 +22,16 @@
665         self.failIf(IDirnodeURI.providedBy(u2))
666         self.failUnlessEqual(u2.data, data)
667         self.failUnlessEqual(u2.get_size(), len(data))
668-        self.failUnless(u.is_readonly())
669-        self.failIf(u.is_mutable())
670+        self.failUnless(u2.is_readonly())
671+        self.failIf(u2.is_mutable())
672+
673+        u2i = uri.from_string(u.to_string(), deep_immutable=True)
674+        self.failUnless(IFileURI.providedBy(u2i))
675+        self.failIf(IDirnodeURI.providedBy(u2i))
676+        self.failUnlessEqual(u2i.data, data)
677+        self.failUnlessEqual(u2i.get_size(), len(data))
678+        self.failUnless(u2i.is_readonly())
679+        self.failIf(u2i.is_mutable())
680 
681         u3 = u.get_readonly()
682         self.failUnlessIdentical(u, u3)
683@@ -51,18 +59,36 @@
684         fileURI = 'URI:CHK:f5ahxa25t4qkktywz6teyfvcx4:opuioq7tj2y6idzfp6cazehtmgs5fdcebcz3cygrxyydvcozrmeq:3:10:345834'
685         chk1 = uri.CHKFileURI.init_from_string(fileURI)
686         chk2 = uri.CHKFileURI.init_from_string(fileURI)
687+        unk = uri.UnknownURI("lafs://from_the_future")
688         self.failIfEqual(lit1, chk1)
689         self.failUnlessEqual(chk1, chk2)
690         self.failIfEqual(chk1, "not actually a URI")
691         # these should be hashable too
692-        s = set([lit1, chk1, chk2])
693-        self.failUnlessEqual(len(s), 2) # since chk1==chk2
694+        s = set([lit1, chk1, chk2, unk])
695+        self.failUnlessEqual(len(s), 3) # since chk1==chk2
696 
697     def test_is_uri(self):
698         lit1 = uri.LiteralFileURI("some data").to_string()
699         self.failUnless(uri.is_uri(lit1))
700         self.failIf(uri.is_uri(None))
701 
702+    def test_is_literal_file_uri(self):
703+        lit1 = uri.LiteralFileURI("some data").to_string()
704+        self.failUnless(uri.is_literal_file_uri(lit1))
705+        self.failIf(uri.is_literal_file_uri(None))
706+        self.failIf(uri.is_literal_file_uri("foo"))
707+        self.failIf(uri.is_literal_file_uri("ro.foo"))
708+        self.failIf(uri.is_literal_file_uri("URI:LITfoo"))
709+        self.failUnless(uri.is_literal_file_uri("ro.URI:LIT:foo"))
710+        self.failUnless(uri.is_literal_file_uri("imm.URI:LIT:foo"))
711+
712+    def test_has_uri_prefix(self):
713+        self.failUnless(uri.has_uri_prefix("URI:foo"))
714+        self.failUnless(uri.has_uri_prefix("ro.URI:foo"))
715+        self.failUnless(uri.has_uri_prefix("imm.URI:foo"))
716+        self.failIf(uri.has_uri_prefix(None))
717+        self.failIf(uri.has_uri_prefix("foo"))
718+
719 class CHKFile(unittest.TestCase):
720     def test_pack(self):
721         key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
722@@ -88,8 +114,7 @@
723         self.failUnless(IFileURI.providedBy(u))
724         self.failIf(IDirnodeURI.providedBy(u))
725         self.failUnlessEqual(u.get_size(), 1234)
726-        self.failUnless(u.is_readonly())
727-        self.failIf(u.is_mutable())
728+
729         u_ro = u.get_readonly()
730         self.failUnlessIdentical(u, u_ro)
731         he = u.to_human_encoding()
732@@ -109,11 +134,19 @@
733         self.failUnless(IFileURI.providedBy(u2))
734         self.failIf(IDirnodeURI.providedBy(u2))
735         self.failUnlessEqual(u2.get_size(), 1234)
736-        self.failUnless(u2.is_readonly())
737-        self.failIf(u2.is_mutable())
738+
739+        u2i = uri.from_string(u.to_string(), deep_immutable=True)
740+        self.failUnlessEqual(u.to_string(), u2i.to_string())
741+        u2ro = uri.from_string(uri.ALLEGED_READONLY_PREFIX + u.to_string())
742+        self.failUnlessEqual(u.to_string(), u2ro.to_string())
743+        u2imm = uri.from_string(uri.ALLEGED_IMMUTABLE_PREFIX + u.to_string())
744+        self.failUnlessEqual(u.to_string(), u2imm.to_string())
745 
746         v = u.get_verify_cap()
747         self.failUnless(isinstance(v.to_string(), str))
748+        self.failUnless(v.is_readonly())
749+        self.failIf(v.is_mutable())
750+
751         v2 = uri.from_string(v.to_string())
752         self.failUnlessEqual(v, v2)
753         he = v.to_human_encoding()
754@@ -126,6 +159,8 @@
755                                     total_shares=10,
756                                     size=1234)
757         self.failUnless(isinstance(v3.to_string(), str))
758+        self.failUnless(v3.is_readonly())
759+        self.failIf(v3.is_mutable())
760 
761     def test_pack_badly(self):
762         key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
763@@ -179,13 +214,20 @@
764         self.failUnlessEqual(readable["UEB_hash"],
765                              base32.b2a(hashutil.uri_extension_hash(ext)))
766 
767-class Invalid(unittest.TestCase):
768+class Unknown(unittest.TestCase):
769     def test_from_future(self):
770         # any URI type that we don't recognize should be treated as unknown
771         future_uri = "I am a URI from the future. Whatever you do, don't "
772         u = uri.from_string(future_uri)
773         self.failUnless(isinstance(u, uri.UnknownURI))
774         self.failUnlessEqual(u.to_string(), future_uri)
775+        self.failUnless(u.get_readonly() is None)
776+        self.failUnless(u.get_error() is None)
777+
778+        u2 = uri.UnknownURI(future_uri, error=CapConstraintError("..."))
779+        self.failUnlessEqual(u.to_string(), future_uri)
780+        self.failUnless(u2.get_readonly() is None)
781+        self.failUnless(isinstance(u2.get_error(), CapConstraintError))
782 
783 class Constraint(unittest.TestCase):
784     def test_constraint(self):
785@@ -226,6 +268,13 @@
786         self.failUnless(IMutableFileURI.providedBy(u2))
787         self.failIf(IDirnodeURI.providedBy(u2))
788 
789+        u2i = uri.from_string(u.to_string(), deep_immutable=True)
790+        self.failUnless(isinstance(u2i, uri.UnknownURI), u2i)
791+        u2ro = uri.from_string(uri.ALLEGED_READONLY_PREFIX + u.to_string())
792+        self.failUnless(isinstance(u2ro, uri.UnknownURI), u2ro)
793+        u2imm = uri.from_string(uri.ALLEGED_IMMUTABLE_PREFIX + u.to_string())
794+        self.failUnless(isinstance(u2imm, uri.UnknownURI), u2imm)
795+
796         u3 = u2.get_readonly()
797         readkey = hashutil.ssk_readkey_hash(writekey)
798         self.failUnlessEqual(u3.fingerprint, fingerprint)
799@@ -236,6 +285,13 @@
800         self.failUnless(IMutableFileURI.providedBy(u3))
801         self.failIf(IDirnodeURI.providedBy(u3))
802 
803+        u3i = uri.from_string(u3.to_string(), deep_immutable=True)
804+        self.failUnless(isinstance(u3i, uri.UnknownURI), u3i)
805+        u3ro = uri.from_string(uri.ALLEGED_READONLY_PREFIX + u3.to_string())
806+        self.failUnlessEqual(u3.to_string(), u3ro.to_string())
807+        u3imm = uri.from_string(uri.ALLEGED_IMMUTABLE_PREFIX + u3.to_string())
808+        self.failUnless(isinstance(u3imm, uri.UnknownURI), u3imm)
809+
810         he = u3.to_human_encoding()
811         u3_h = uri.ReadonlySSKFileURI.init_from_human_encoding(he)
812         self.failUnlessEqual(u3, u3_h)
813@@ -249,6 +305,13 @@
814         self.failUnless(IMutableFileURI.providedBy(u4))
815         self.failIf(IDirnodeURI.providedBy(u4))
816 
817+        u4i = uri.from_string(u4.to_string(), deep_immutable=True)
818+        self.failUnless(isinstance(u4i, uri.UnknownURI), u4i)
819+        u4ro = uri.from_string(uri.ALLEGED_READONLY_PREFIX + u4.to_string())
820+        self.failUnlessEqual(u4.to_string(), u4ro.to_string())
821+        u4imm = uri.from_string(uri.ALLEGED_IMMUTABLE_PREFIX + u4.to_string())
822+        self.failUnless(isinstance(u4imm, uri.UnknownURI), u4imm)
823+
824         u4a = uri.from_string(u4.to_string())
825         self.failUnlessEqual(u4a, u4)
826         self.failUnless("ReadonlySSKFileURI" in str(u4a))
827@@ -291,12 +354,19 @@
828         self.failIf(IFileURI.providedBy(u2))
829         self.failUnless(IDirnodeURI.providedBy(u2))
830 
831+        u2i = uri.from_string(u1.to_string(), deep_immutable=True)
832+        self.failUnless(isinstance(u2i, uri.UnknownURI))
833+
834         u3 = u2.get_readonly()
835         self.failUnless(u3.is_readonly())
836         self.failUnless(u3.is_mutable())
837         self.failUnless(IURI.providedBy(u3))
838         self.failIf(IFileURI.providedBy(u3))
839         self.failUnless(IDirnodeURI.providedBy(u3))
840+
841+        u3i = uri.from_string(u2.to_string(), deep_immutable=True)
842+        self.failUnless(isinstance(u3i, uri.UnknownURI))
843+
844         u3n = u3._filenode_uri
845         self.failUnless(u3n.is_readonly())
846         self.failUnless(u3n.is_mutable())
847@@ -363,10 +433,16 @@
848         self.failIf(IFileURI.providedBy(u2))
849         self.failUnless(IDirnodeURI.providedBy(u2))
850 
851+        u2i = uri.from_string(u1.to_string(), deep_immutable=True)
852+        self.failUnlessEqual(u1.to_string(), u2i.to_string())
853+
854         u3 = u2.get_readonly()
855         self.failUnlessEqual(u3.to_string(), u2.to_string())
856         self.failUnless(str(u3))
857 
858+        u3i = uri.from_string(u2.to_string(), deep_immutable=True)
859+        self.failUnlessEqual(u2.to_string(), u3i.to_string())
860+
861         u2_verifier = u2.get_verify_cap()
862         self.failUnless(isinstance(u2_verifier,
863                                    uri.ImmutableDirectoryURIVerifier),
864diff -rN -u old-tahoe/src/allmydata/test/test_web.py new-tahoe/src/allmydata/test/test_web.py
865--- old-tahoe/src/allmydata/test/test_web.py    2010-01-23 12:59:05.171000000 +0000
866+++ new-tahoe/src/allmydata/test/test_web.py    2010-01-23 12:59:05.639000000 +0000
867@@ -7,7 +7,7 @@
868 from twisted.web import client, error, http
869 from twisted.python import failure, log
870 from nevow import rend
871-from allmydata import interfaces, uri, webish
872+from allmydata import interfaces, uri, webish, dirnode
873 from allmydata.storage.shares import get_share_file
874 from allmydata.storage_client import StorageFarmBroker
875 from allmydata.immutable import upload, download
876@@ -18,6 +18,7 @@
877 from allmydata.scripts.debug import CorruptShareOptions, corrupt_share
878 from allmydata.util import fileutil, base32
879 from allmydata.util.consumer import download_to_data
880+from allmydata.util.netstring import split_netstring
881 from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \
882      create_chk_filenode, WebErrorMixin, ShouldFailMixin, make_mutable_file_uri
883 from allmydata.interfaces import IMutableFileNode
884@@ -366,25 +367,101 @@
885             self.fail("%s was supposed to Error(404), not get '%s'" %
886                       (which, res))
887 
888+    def _dump_res(self, res):
889+        import traceback
890+        s = "%r\n" % (res,)
891+        if hasattr(res, 'tb_frame'):
892+            s += "Traceback:\n%s\n" % (traceback.format_tb(res),)
893+        if hasattr(res, 'value'):
894+            s += "%r\n" % (res.value,)
895+            if hasattr(res.value, 'tb_frame'):
896+                s += "Traceback:\n%s\n" % (res, res.value, traceback.format_tb(res))
897+            if hasattr(res.value, 'response'):
898+                s += "Response body:\n%s\n" % (res.value.response,)
899+        return s
900+
901+    def shouldSucceedGET(self, urlpath, followRedirect=False,
902+                         expected_statuscode=http.OK, return_response=False, **kwargs):
903+        d = self.GET(urlpath, followRedirect=followRedirect, return_response=True, **kwargs)
904+        def done((res, statuscode, headers)):
905+            if isinstance(res, failure.Failure):
906+                self.fail(("'GET %s' with kwargs %r was supposed to succeed with statuscode %s, "
907+                           "but it failed with statuscode %s instead.\n"
908+                           "%s\nThe response headers were:\n%s") % (
909+                               urlpath, kwargs, expected_statuscode, statuscode,
910+                               self._dump_res(res), headers))
911+            if str(statuscode) != str(expected_statuscode):
912+                self.fail(("'GET %s' with kwargs %r was supposed to succeed with statuscode %s, "
913+                            "but it succeeded with statuscode %s instead.\n"
914+                            "The response headers were:\n%s\n\n"
915+                            "The response body was:\n%s") % (
916+                                urlpath, kwargs, expected_statuscode, statuscode, headers, res))
917+            if return_response:
918+                return (res, statuscode, headers)
919+            else:
920+                return res
921+        d.addBoth(done)
922+        return d
923+
924+    def shouldSucceedHEAD(self, urlpath, expected_statuscode=http.OK,
925+                          return_response=False, **kwargs):
926+        d = self.HEAD(urlpath, return_response=True, **kwargs)
927+        def done((res, statuscode, headers)):
928+            if isinstance(res, failure.Failure):
929+                self.fail(("'HEAD %s' with kwargs %r was supposed to succeed with statuscode %s, "
930+                           "but it failed with statuscode %s instead.\n"
931+                           "%s\nThe response headers were:\n%s") % (
932+                               urlpath, kwargs, expected_statuscode, statuscode,
933+                               self._dump_res(res), headers))
934+            if str(statuscode) != str(expected_statuscode):
935+                self.fail(("'HEAD %s' with kwargs %r was supposed to succeed with statuscode %s, "
936+                            "but it succeeded with statuscode %s instead.\n"
937+                            "The response headers were:\n%s\n\n"
938+                            "The response body was:\n%s") % (
939+                                urlpath, kwargs, expected_statuscode, statuscode, headers, res))
940+            if return_response:
941+                return (res, statuscode, headers)
942+            else:
943+                return res
944+        d.addBoth(done)
945+        return d
946+
947+    def shouldSucceed(self, which, expected_statuscode, callable, *args, **kwargs):
948+        d = defer.maybeDeferred(callable, *args, **kwargs)
949+        def done(res):
950+            if isinstance(res, failure.Failure):
951+                self.fail(("%s:\nAn HTTP op with args %r and kwargs %r was supposed to "
952+                           "succeed with statuscode %s, but it failed:\n%s") % (
953+                               which, args, kwargs, expected_statuscode,
954+                               self._dump_res(res)))
955+            #if str(statuscode) != str(expected_statuscode):
956+            #    self.fail(("%s:\nAn HTTP op with args %r and kwargs %r was supposed to "
957+            #               "succeed with statuscode %s, but it succeeded with statuscode %s instead.\n"
958+            #               "The response body was:\n%s") % (
959+            #                   which, args, kwargs, expected_statuscode, statuscode, res))
960+            return res
961+        d.addBoth(done)
962+        return d
963+
964 
965 class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
966     def test_create(self):
967         pass
968 
969     def test_welcome(self):
970-        d = self.GET("/")
971+        d = self.shouldSucceedGET("/")
972         def _check(res):
973             self.failUnless('Welcome To Tahoe-LAFS' in res, res)
974 
975             self.s.basedir = 'web/test_welcome'
976             fileutil.make_dirs("web/test_welcome")
977             fileutil.make_dirs("web/test_welcome/private")
978-            return self.GET("/")
979+            return self.shouldSucceedGET("/")
980         d.addCallback(_check)
981         return d
982 
983     def test_provisioning(self):
984-        d = self.GET("/provisioning/")
985+        d = self.shouldSucceedGET("/provisioning/")
986         def _check(res):
987             self.failUnless('Tahoe Provisioning Tool' in res)
988             fields = {'filled': True,
989@@ -400,9 +477,10 @@
990                       "delete_rate": 10,
991                       "lease_timer": 7,
992                       }
993-            return self.POST("/provisioning/", **fields)
994-
995+            return self.shouldSucceed("POST_provisioning-1", http.OK, self.POST,
996+                                      "/provisioning/", **fields)
997         d.addCallback(_check)
998+
999         def _check2(res):
1000             self.failUnless('Tahoe Provisioning Tool' in res)
1001             self.failUnless("Share space consumed: 167.01TB" in res)
1002@@ -422,13 +500,17 @@
1003                       "delete_rate": 100,
1004                       "lease_timer": 7,
1005                       }
1006-            return self.POST("/provisioning/", **fields)
1007+            return self.shouldSucceed("POST_provisioning-2", http.OK, self.POST,
1008+                                      "/provisioning/", **fields)
1009         d.addCallback(_check2)
1010+
1011         def _check3(res):
1012             self.failUnless("Share space consumed: huge!" in res)
1013             fields = {'filled': True}
1014-            return self.POST("/provisioning/", **fields)
1015+            return self.shouldSucceed("POST_provisioning-3", http.OK, self.POST,
1016+                                      "/provisioning/", **fields)
1017         d.addCallback(_check3)
1018+
1019         def _check4(res):
1020             self.failUnless("Share space consumed:" in res)
1021         d.addCallback(_check4)
1022@@ -442,7 +524,7 @@
1023         except:
1024             raise unittest.SkipTest("reliability tool requires NumPy")
1025 
1026-        d = self.GET("/reliability/")
1027+        d = self.shouldSucceedGET("/reliability/")
1028         def _check(res):
1029             self.failUnless('Tahoe Reliability Tool' in res)
1030             fields = {'drive_lifetime': "8Y",
1031@@ -471,7 +553,7 @@
1032         mu_num = h.list_all_mapupdate_statuses()[0].get_counter()
1033         pub_num = h.list_all_publish_statuses()[0].get_counter()
1034         ret_num = h.list_all_retrieve_statuses()[0].get_counter()
1035-        d = self.GET("/status", followRedirect=True)
1036+        d = self.shouldSucceedGET("/status", followRedirect=True)
1037         def _check(res):
1038             self.failUnless('Upload and Download Status' in res, res)
1039             self.failUnless('"down-%d"' % dl_num in res, res)
1040@@ -480,7 +562,7 @@
1041             self.failUnless('"publish-%d"' % pub_num in res, res)
1042             self.failUnless('"retrieve-%d"' % ret_num in res, res)
1043         d.addCallback(_check)
1044-        d.addCallback(lambda res: self.GET("/status/?t=json"))
1045+        d.addCallback(lambda res: self.shouldSucceedGET("/status/?t=json"))
1046         def _check_json(res):
1047             data = simplejson.loads(res)
1048             self.failUnless(isinstance(data, dict))
1049@@ -489,23 +571,23 @@
1050             # here.
1051         d.addCallback(_check_json)
1052 
1053-        d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
1054+        d.addCallback(lambda res: self.shouldSucceedGET("/status/down-%d" % dl_num))
1055         def _check_dl(res):
1056             self.failUnless("File Download Status" in res, res)
1057         d.addCallback(_check_dl)
1058-        d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
1059+        d.addCallback(lambda res: self.shouldSucceedGET("/status/up-%d" % ul_num))
1060         def _check_ul(res):
1061             self.failUnless("File Upload Status" in res, res)
1062         d.addCallback(_check_ul)
1063-        d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num))
1064+        d.addCallback(lambda res: self.shouldSucceedGET("/status/mapupdate-%d" % mu_num))
1065         def _check_mapupdate(res):
1066             self.failUnless("Mutable File Servermap Update Status" in res, res)
1067         d.addCallback(_check_mapupdate)
1068-        d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num))
1069+        d.addCallback(lambda res: self.shouldSucceedGET("/status/publish-%d" % pub_num))
1070         def _check_publish(res):
1071             self.failUnless("Mutable File Publish Status" in res, res)
1072         d.addCallback(_check_publish)
1073-        d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num))
1074+        d.addCallback(lambda res: self.shouldSucceedGET("/status/retrieve-%d" % ret_num))
1075         def _check_retrieve(res):
1076             self.failUnless("Mutable File Retrieve Status" in res, res)
1077         d.addCallback(_check_retrieve)
1078@@ -536,16 +618,15 @@
1079         self.failUnlessEqual(urrm.render_rate(None, 123), "123Bps")
1080 
1081     def test_GET_FILEURL(self):
1082-        d = self.GET(self.public_url + "/foo/bar.txt")
1083+        d = self.shouldSucceedGET(self.public_url + "/foo/bar.txt")
1084         d.addCallback(self.failUnlessIsBarDotTxt)
1085         return d
1086 
1087     def test_GET_FILEURL_range(self):
1088         headers = {"range": "bytes=1-10"}
1089-        d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
1090-                     return_response=True)
1091-        def _got((res, status, headers)):
1092-            self.failUnlessEqual(int(status), 206)
1093+        d = self.shouldSucceedGET(self.public_url + "/foo/bar.txt", headers=headers,
1094+                                  expected_statuscode=http.PARTIAL_CONTENT, return_response=True)
1095+        def _got((res, statuscode, headers)):
1096             self.failUnless(headers.has_key("content-range"))
1097             self.failUnlessEqual(headers["content-range"][0],
1098                                  "bytes 1-10/%d" % len(self.BAR_CONTENTS))
1099@@ -556,10 +637,9 @@
1100     def test_GET_FILEURL_partial_range(self):
1101         headers = {"range": "bytes=5-"}
1102         length  = len(self.BAR_CONTENTS)
1103-        d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
1104-                     return_response=True)
1105-        def _got((res, status, headers)):
1106-            self.failUnlessEqual(int(status), 206)
1107+        d = self.shouldSucceedGET(self.public_url + "/foo/bar.txt", headers=headers,
1108+                                  expected_statuscode=http.PARTIAL_CONTENT, return_response=True)
1109+        def _got((res, statuscode, headers)):
1110             self.failUnless(headers.has_key("content-range"))
1111             self.failUnlessEqual(headers["content-range"][0],
1112                                  "bytes 5-%d/%d" % (length-1, length))
1113@@ -569,11 +649,10 @@
1114 
1115     def test_HEAD_FILEURL_range(self):
1116         headers = {"range": "bytes=1-10"}
1117-        d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
1118-                     return_response=True)
1119-        def _got((res, status, headers)):
1120+        d = self.shouldSucceedHEAD(self.public_url + "/foo/bar.txt", headers=headers,
1121+                                   expected_statuscode=http.PARTIAL_CONTENT, return_response=True)
1122+        def _got((res, statuscode, headers)):
1123             self.failUnlessEqual(res, "")
1124-            self.failUnlessEqual(int(status), 206)
1125             self.failUnless(headers.has_key("content-range"))
1126             self.failUnlessEqual(headers["content-range"][0],
1127                                  "bytes 1-10/%d" % len(self.BAR_CONTENTS))
1128@@ -583,10 +662,9 @@
1129     def test_HEAD_FILEURL_partial_range(self):
1130         headers = {"range": "bytes=5-"}
1131         length  = len(self.BAR_CONTENTS)
1132-        d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
1133-                     return_response=True)
1134-        def _got((res, status, headers)):
1135-            self.failUnlessEqual(int(status), 206)
1136+        d = self.shouldSucceedHEAD(self.public_url + "/foo/bar.txt", headers=headers,
1137+                                   expected_statuscode=http.PARTIAL_CONTENT, return_response=True)
1138+        def _got((res, statuscode, headers)):
1139             self.failUnless(headers.has_key("content-range"))
1140             self.failUnlessEqual(headers["content-range"][0],
1141                                  "bytes 5-%d/%d" % (length-1, length))
1142@@ -595,7 +673,7 @@
1143 
1144     def test_GET_FILEURL_range_bad(self):
1145         headers = {"range": "BOGUS=fizbop-quarnak"}
1146-        d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_bad",
1147+        d = self.shouldFail2(error.Error, "GET_FILEURL_range_bad",
1148                              "400 Bad Request",
1149                              "Syntactically invalid http range header",
1150                              self.GET, self.public_url + "/foo/bar.txt",
1151@@ -603,8 +681,9 @@
1152         return d
1153 
1154     def test_HEAD_FILEURL(self):
1155-        d = self.HEAD(self.public_url + "/foo/bar.txt", return_response=True)
1156-        def _got((res, status, headers)):
1157+        d = self.shouldSucceedHEAD(self.public_url + "/foo/bar.txt",
1158+                                   expected_statuscode=http.OK, return_response=True)
1159+        def _got((res, statuscode, headers)):
1160             self.failUnlessEqual(res, "")
1161             self.failUnlessEqual(headers["content-length"][0],
1162                                  str(len(self.BAR_CONTENTS)))
1163@@ -615,27 +694,27 @@
1164     def test_GET_FILEURL_named(self):
1165         base = "/file/%s" % urllib.quote(self._bar_txt_uri)
1166         base2 = "/named/%s" % urllib.quote(self._bar_txt_uri)
1167-        d = self.GET(base + "/@@name=/blah.txt")
1168+        d = self.shouldSucceedGET(base + "/@@name=/blah.txt")
1169         d.addCallback(self.failUnlessIsBarDotTxt)
1170-        d.addCallback(lambda res: self.GET(base + "/blah.txt"))
1171+        d.addCallback(lambda res: self.shouldSucceedGET(base + "/blah.txt"))
1172         d.addCallback(self.failUnlessIsBarDotTxt)
1173-        d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt"))
1174+        d.addCallback(lambda res: self.shouldSucceedGET(base + "/ignore/lots/blah.txt"))
1175         d.addCallback(self.failUnlessIsBarDotTxt)
1176-        d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt"))
1177+        d.addCallback(lambda res: self.shouldSucceedGET(base2 + "/@@name=/blah.txt"))
1178         d.addCallback(self.failUnlessIsBarDotTxt)
1179         save_url = base + "?save=true&filename=blah.txt"
1180-        d.addCallback(lambda res: self.GET(save_url))
1181+        d.addCallback(lambda res: self.shouldSucceedGET(save_url))
1182         d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
1183         u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1184         u_fn_e = urllib.quote(u_filename.encode("utf-8"))
1185         u_url = base + "?save=true&filename=" + u_fn_e
1186-        d.addCallback(lambda res: self.GET(u_url))
1187+        d.addCallback(lambda res: self.shouldSucceedGET(u_url))
1188         d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
1189         return d
1190 
1191     def test_PUT_FILEURL_named_bad(self):
1192         base = "/file/%s" % urllib.quote(self._bar_txt_uri)
1193-        d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad",
1194+        d = self.shouldFail2(error.Error, "PUT_FILEURL_named_bad",
1195                              "400 Bad Request",
1196                              "/file can only be used with GET or HEAD",
1197                              self.PUT, base + "/@@name=/blah.txt", "")
1198@@ -643,14 +722,14 @@
1199 
1200     def test_GET_DIRURL_named_bad(self):
1201         base = "/file/%s" % urllib.quote(self._foo_uri)
1202-        d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
1203+        d = self.shouldFail2(error.Error, "PUT_DIRURL_named_bad",
1204                              "400 Bad Request",
1205                              "is not a file-cap",
1206                              self.GET, base + "/@@name=/blah.txt")
1207         return d
1208 
1209     def test_GET_slash_file_bad(self):
1210-        d = self.shouldFail2(error.Error, "test_GET_slash_file_bad",
1211+        d = self.shouldFail2(error.Error, "GET_slash_file_bad",
1212                              "404 Not Found",
1213                              "/file must be followed by a file-cap and a name",
1214                              self.GET, "/file")
1215@@ -671,7 +750,7 @@
1216         verifier_cap = n.get_verify_cap().to_string()
1217         base = "/uri/%s" % urllib.quote(verifier_cap)
1218         # client.create_node_from_uri() can't handle verify-caps
1219-        d = self.shouldFail2(error.Error, "test_GET_unhandled_URI",
1220+        d = self.shouldFail2(error.Error, "GET_unhandled_URI",
1221                              "400 Bad Request",
1222                              "GET unknown URI type: can only do t=info",
1223                              self.GET, base)
1224@@ -679,14 +758,14 @@
1225 
1226     def test_GET_FILE_URI(self):
1227         base = "/uri/%s" % urllib.quote(self._bar_txt_uri)
1228-        d = self.GET(base)
1229+        d = self.shouldSucceedGET(base)
1230         d.addCallback(self.failUnlessIsBarDotTxt)
1231         return d
1232 
1233     def test_GET_FILE_URI_badchild(self):
1234         base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
1235         errmsg = "Files have no children, certainly not named 'boguschild'"
1236-        d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
1237+        d = self.shouldFail2(error.Error, "GET_FILE_URI_badchild",
1238                              "400 Bad Request", errmsg,
1239                              self.GET, base)
1240         return d
1241@@ -694,35 +773,42 @@
1242     def test_PUT_FILE_URI_badchild(self):
1243         base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
1244         errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory"
1245-        d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
1246+        d = self.shouldFail2(error.Error, "GET_FILE_URI_badchild",
1247                              "400 Bad Request", errmsg,
1248                              self.PUT, base, "")
1249         return d
1250 
1251+    # TODO: version of this with a Unicode filename
1252     def test_GET_FILEURL_save(self):
1253-        d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true")
1254-        # TODO: look at the headers, expect a Content-Disposition: attachment
1255-        # header.
1256-        d.addCallback(self.failUnlessIsBarDotTxt)
1257+        d = self.shouldSucceedGET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true",
1258+                                  return_response=True)
1259+        def _got((res, statuscode, headers)):
1260+            content_disposition = headers["content-disposition"][0]
1261+            self.failUnless(content_disposition == 'attachment; filename="bar.txt"', content_disposition)
1262+            self.failUnlessIsBarDotTxt(res)
1263+        d.addCallback(_got)
1264         return d
1265 
1266     def test_GET_FILEURL_missing(self):
1267         d = self.GET(self.public_url + "/foo/missing")
1268-        d.addBoth(self.should404, "test_GET_FILEURL_missing")
1269+        d.addBoth(self.should404, "GET_FILEURL_missing")
1270         return d
1271 
1272     def test_PUT_overwrite_only_files(self):
1273         # create a directory, put a file in that directory.
1274         contents, n, filecap = self.makefile(8)
1275-        d = self.PUT(self.public_url + "/foo/dir?t=mkdir", "")
1276+        d = self.shouldSucceed("PUT_overwrite_only_files_1", http.OK, self.PUT,
1277+                               self.public_url + "/foo/dir?t=mkdir", "")
1278         d.addCallback(lambda res:
1279-            self.PUT(self.public_url + "/foo/dir/file1.txt",
1280-                     self.NEWFILE_CONTENTS))
1281+            self.shouldSucceed("PUT_overwrite_only_files_2", http.OK, self.PUT,
1282+                               self.public_url + "/foo/dir/file1.txt",
1283+                               self.NEWFILE_CONTENTS))
1284         # try to overwrite the file with replace=only-files
1285         # (this should work)
1286         d.addCallback(lambda res:
1287-            self.PUT(self.public_url + "/foo/dir/file1.txt?t=uri&replace=only-files",
1288-                     filecap))
1289+            self.shouldSucceed("PUT_overwrite_only_files_3", http.OK, self.PUT,
1290+                               self.public_url + "/foo/dir/file1.txt?t=uri&replace=only-files",
1291+                               filecap))
1292         d.addCallback(lambda res:
1293             self.shouldFail2(error.Error, "PUT_bad_t", "409 Conflict",
1294                  "There was already a child by that name, and you asked me "
1295@@ -732,21 +818,19 @@
1296         return d
1297 
1298     def test_PUT_NEWFILEURL(self):
1299-        d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
1300-        # TODO: we lose the response code, so we can't check this
1301-        #self.failUnlessEqual(responsecode, 201)
1302-        d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
1303+        d = self.shouldSucceed("PUT_NEWFILEURL", http.CREATED, self.PUT,
1304+                               self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
1305+        d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
1306         d.addCallback(lambda res:
1307                       self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1308                                                       self.NEWFILE_CONTENTS))
1309         return d
1310 
1311     def test_PUT_NEWFILEURL_not_mutable(self):
1312-        d = self.PUT(self.public_url + "/foo/new.txt?mutable=false",
1313-                     self.NEWFILE_CONTENTS)
1314-        # TODO: we lose the response code, so we can't check this
1315-        #self.failUnlessEqual(responsecode, 201)
1316-        d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
1317+        d = self.shouldSucceed("PUT_NEWFILEURL_not_mutable", http.CREATED, self.PUT,
1318+                               self.public_url + "/foo/new.txt?mutable=false",
1319+                               self.NEWFILE_CONTENTS)
1320+        d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
1321         d.addCallback(lambda res:
1322                       self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1323                                                       self.NEWFILE_CONTENTS))
1324@@ -755,7 +839,7 @@
1325     def test_PUT_NEWFILEURL_range_bad(self):
1326         headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
1327         target = self.public_url + "/foo/new.txt"
1328-        d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad",
1329+        d = self.shouldFail2(error.Error, "PUT_NEWFILEURL_range_bad",
1330                              "501 Not Implemented",
1331                              "Content-Range in PUT not yet supported",
1332                              # (and certainly not for immutable files)
1333@@ -766,17 +850,16 @@
1334         return d
1335 
1336     def test_PUT_NEWFILEURL_mutable(self):
1337-        d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
1338-                     self.NEWFILE_CONTENTS)
1339-        # TODO: we lose the response code, so we can't check this
1340-        #self.failUnlessEqual(responsecode, 201)
1341+        d = self.shouldSucceed("PUT_NEWFILEURL_mutable", http.CREATED, self.PUT,
1342+                               self.public_url + "/foo/new.txt?mutable=true",
1343+                               self.NEWFILE_CONTENTS)
1344         def _check_uri(res):
1345             u = uri.from_string_mutable_filenode(res)
1346             self.failUnless(u.is_mutable())
1347             self.failIf(u.is_readonly())
1348             return res
1349         d.addCallback(_check_uri)
1350-        d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
1351+        d.addCallback(self.failUnlessURIMatchesRWChild, self._foo_node, u"new.txt")
1352         d.addCallback(lambda res:
1353                       self.failUnlessMutableChildContentsAre(self._foo_node,
1354                                                              u"new.txt",
1355@@ -784,7 +867,7 @@
1356         return d
1357 
1358     def test_PUT_NEWFILEURL_mutable_toobig(self):
1359-        d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_mutable_toobig",
1360+        d = self.shouldFail2(error.Error, "PUT_NEWFILEURL_mutable_toobig",
1361                              "413 Request Entity Too Large",
1362                              "SDMF is limited to one segment, and 10001 > 10000",
1363                              self.PUT,
1364@@ -793,10 +876,9 @@
1365         return d
1366 
1367     def test_PUT_NEWFILEURL_replace(self):
1368-        d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
1369-        # TODO: we lose the response code, so we can't check this
1370-        #self.failUnlessEqual(responsecode, 200)
1371-        d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
1372+        d = self.shouldSucceed("PUT_NEWFILEURL_replace", http.OK, self.PUT,
1373+                               self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
1374+        d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
1375         d.addCallback(lambda res:
1376                       self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1377                                                       self.NEWFILE_CONTENTS))
1378@@ -819,9 +901,11 @@
1379         return d
1380 
1381     def test_PUT_NEWFILEURL_mkdirs(self):
1382-        d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
1383+        d = self.shouldSucceed("PUT_NEWFILEURL_mkdirs", http.OK, self.PUT,
1384+                               self.public_url + "/foo/newdir/new.txt",
1385+                               self.NEWFILE_CONTENTS)
1386         fn = self._foo_node
1387-        d.addCallback(self.failUnlessURIMatchesChild, fn, u"newdir/new.txt")
1388+        d.addCallback(self.failUnlessURIMatchesROChild, fn, u"newdir/new.txt")
1389         d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
1390         d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
1391         d.addCallback(lambda res:
1392@@ -839,26 +923,27 @@
1393 
1394     def test_PUT_NEWFILEURL_emptyname(self):
1395         # an empty pathname component (i.e. a double-slash) is disallowed
1396-        d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_emptyname",
1397+        d = self.shouldFail2(error.Error, "PUT_NEWFILEURL_emptyname",
1398                              "400 Bad Request",
1399                              "The webapi does not allow empty pathname components",
1400                              self.PUT, self.public_url + "/foo//new.txt", "")
1401         return d
1402 
1403     def test_DELETE_FILEURL(self):
1404-        d = self.DELETE(self.public_url + "/foo/bar.txt")
1405+        d = self.shouldSucceed("DELETE_FILEURL", http.OK, self.DELETE,
1406+                               self.public_url + "/foo/bar.txt")
1407         d.addCallback(lambda res:
1408                       self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1409         return d
1410 
1411     def test_DELETE_FILEURL_missing(self):
1412         d = self.DELETE(self.public_url + "/foo/missing")
1413-        d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
1414+        d.addBoth(self.should404, "DELETE_FILEURL_missing")
1415         return d
1416 
1417     def test_DELETE_FILEURL_missing2(self):
1418         d = self.DELETE(self.public_url + "/missing/missing")
1419-        d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
1420+        d.addBoth(self.should404, "DELETE_FILEURL_missing2")
1421         return d
1422 
1423     def failUnlessHasBarDotTxtMetadata(self, res):
1424@@ -875,7 +960,7 @@
1425         # I can't do "GET /path?json", I have to do "GET /path/t=json"
1426         # instead. This may make it tricky to emulate the S3 interface
1427         # completely.
1428-        d = self.GET(self.public_url + "/foo/bar.txt?t=json")
1429+        d = self.shouldSucceedGET(self.public_url + "/foo/bar.txt?t=json")
1430         def _check1(data):
1431             self.failUnlessIsBarJSON(data)
1432             self.failUnlessHasBarDotTxtMetadata(data)
1433@@ -885,16 +970,16 @@
1434 
1435     def test_GET_FILEURL_json_missing(self):
1436         d = self.GET(self.public_url + "/foo/missing?json")
1437-        d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
1438+        d.addBoth(self.should404, "GET_FILEURL_json_missing")
1439         return d
1440 
1441     def test_GET_FILEURL_uri(self):
1442-        d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
1443+        d = self.shouldSucceedGET(self.public_url + "/foo/bar.txt?t=uri")
1444         def _check(res):
1445             self.failUnlessEqual(res, self._bar_txt_uri)
1446         d.addCallback(_check)
1447         d.addCallback(lambda res:
1448-                      self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
1449+                      self.shouldSucceedGET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
1450         def _check2(res):
1451             # for now, for files, uris and readonly-uris are the same
1452             self.failUnlessEqual(res, self._bar_txt_uri)
1453@@ -910,14 +995,14 @@
1454 
1455     def test_GET_FILEURL_uri_missing(self):
1456         d = self.GET(self.public_url + "/foo/missing?t=uri")
1457-        d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
1458+        d.addBoth(self.should404, "GET_FILEURL_uri_missing")
1459         return d
1460 
1461     def test_GET_DIRURL(self):
1462         # the addSlash means we get a redirect here
1463         # from /uri/$URI/foo/ , we need ../../../ to get back to the root
1464         ROOT = "../../.."
1465-        d = self.GET(self.public_url + "/foo", followRedirect=True)
1466+        d = self.shouldSucceedGET(self.public_url + "/foo", followRedirect=True)
1467         def _check(res):
1468             self.failUnless(('<a href="%s">Return to Welcome page' % ROOT)
1469                             in res, res)
1470@@ -954,9 +1039,9 @@
1471             self.failUnless(re.search(get_sub, res), res)
1472         d.addCallback(_check)
1473 
1474-        # look at a directory which is readonly
1475+        # look at a readonly directory
1476         d.addCallback(lambda res:
1477-                      self.GET(self.public_url + "/reedownlee", followRedirect=True))
1478+                      self.shouldSucceedGET(self.public_url + "/reedownlee", followRedirect=True))
1479         def _check2(res):
1480             self.failUnless("(read-only)" in res, res)
1481             self.failIf("Upload a file" in res, res)
1482@@ -964,14 +1049,14 @@
1483 
1484         # and at a directory that contains a readonly directory
1485         d.addCallback(lambda res:
1486-                      self.GET(self.public_url, followRedirect=True))
1487+                      self.shouldSucceedGET(self.public_url, followRedirect=True))
1488         def _check3(res):
1489             self.failUnless(re.search('<td>DIR-RO</td>'
1490                                       r'\s+<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a></td>', res), res)
1491         d.addCallback(_check3)
1492 
1493         # and an empty directory
1494-        d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty/"))
1495+        d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/empty/"))
1496         def _check4(res):
1497             self.failUnless("directory is empty" in res, res)
1498             MKDIR_BUTTON_RE=re.compile('<input type="hidden" name="t" value="mkdir" />.*<legend class="freeform-form-label">Create a new directory in this directory</legend>.*<input type="submit" value="Create" />', re.I)
1499@@ -981,7 +1066,7 @@
1500         return d
1501 
1502     def test_GET_DIRURL_badtype(self):
1503-        d = self.shouldHTTPError("test_GET_DIRURL_badtype",
1504+        d = self.shouldHTTPError("GET_DIRURL_badtype",
1505                                  400, "Bad Request",
1506                                  "bad t=bogus",
1507                                  self.GET,
1508@@ -989,14 +1074,14 @@
1509         return d
1510 
1511     def test_GET_DIRURL_json(self):
1512-        d = self.GET(self.public_url + "/foo?t=json")
1513+        d = self.shouldSucceedGET(self.public_url + "/foo?t=json")
1514         d.addCallback(self.failUnlessIsFooJSON)
1515         return d
1516 
1517 
1518     def test_POST_DIRURL_manifest_no_ophandle(self):
1519         d = self.shouldFail2(error.Error,
1520-                             "test_POST_DIRURL_manifest_no_ophandle",
1521+                             "POST_DIRURL_manifest_no_ophandle",
1522                              "400 Bad Request",
1523                              "slow operation requires ophandle=",
1524                              self.POST, self.public_url, t="start-manifest")
1525@@ -1005,8 +1090,9 @@
1526     def test_POST_DIRURL_manifest(self):
1527         d = defer.succeed(None)
1528         def getman(ignored, output):
1529-            d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
1530-                          followRedirect=True)
1531+            d = self.shouldSucceed("POST_DIRURL_manifest", http.OK, self.POST,
1532+                                   self.public_url + "/foo/?t=start-manifest&ophandle=125",
1533+                                   followRedirect=True)
1534             d.addCallback(self.wait_for_operation, "125")
1535             d.addCallback(self.get_operation_results, "125", output)
1536             return d
1537@@ -1019,7 +1105,7 @@
1538         d.addCallback(_got_html)
1539 
1540         # both t=status and unadorned GET should be identical
1541-        d.addCallback(lambda res: self.GET("/operations/125"))
1542+        d.addCallback(lambda res: self.shouldSucceedGET("/operations/125"))
1543         d.addCallback(_got_html)
1544 
1545         d.addCallback(getman, "html")
1546@@ -1047,15 +1133,16 @@
1547 
1548     def test_POST_DIRURL_deepsize_no_ophandle(self):
1549         d = self.shouldFail2(error.Error,
1550-                             "test_POST_DIRURL_deepsize_no_ophandle",
1551+                             "POST_DIRURL_deepsize_no_ophandle",
1552                              "400 Bad Request",
1553                              "slow operation requires ophandle=",
1554                              self.POST, self.public_url, t="start-deep-size")
1555         return d
1556 
1557     def test_POST_DIRURL_deepsize(self):
1558-        d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
1559-                      followRedirect=True)
1560+        d = self.shouldSucceed("POST_DIRURL_deepsize", http.OK, self.POST,
1561+                               self.public_url + "/foo/?t=start-deep-size&ophandle=126",
1562+                               followRedirect=True)
1563         d.addCallback(self.wait_for_operation, "126")
1564         d.addCallback(self.get_operation_results, "126", "json")
1565         def _got_json(data):
1566@@ -1075,15 +1162,16 @@
1567 
1568     def test_POST_DIRURL_deepstats_no_ophandle(self):
1569         d = self.shouldFail2(error.Error,
1570-                             "test_POST_DIRURL_deepstats_no_ophandle",
1571+                             "POST_DIRURL_deepstats_no_ophandle",
1572                              "400 Bad Request",
1573                              "slow operation requires ophandle=",
1574                              self.POST, self.public_url, t="start-deep-stats")
1575         return d
1576 
1577     def test_POST_DIRURL_deepstats(self):
1578-        d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
1579-                      followRedirect=True)
1580+        d = self.shouldSucceed("POST_DIRURL_deepstats", http.OK, self.POST,
1581+                               self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
1582+                               followRedirect=True)
1583         d.addCallback(self.wait_for_operation, "127")
1584         d.addCallback(self.get_operation_results, "127", "json")
1585         def _got_json(stats):
1586@@ -1109,7 +1197,8 @@
1587         return d
1588 
1589     def test_POST_DIRURL_stream_manifest(self):
1590-        d = self.POST(self.public_url + "/foo/?t=stream-manifest")
1591+        d = self.shouldSucceed("POST_DIRURL_stream_manifest", http.OK, self.POST,
1592+                               self.public_url + "/foo/?t=stream-manifest")
1593         def _check(res):
1594             self.failUnless(res.endswith("\n"))
1595             units = [simplejson.loads(t) for t in res[:-1].split("\n")]
1596@@ -1129,21 +1218,22 @@
1597         return d
1598 
1599     def test_GET_DIRURL_uri(self):
1600-        d = self.GET(self.public_url + "/foo?t=uri")
1601+        d = self.shouldSucceedGET(self.public_url + "/foo?t=uri")
1602         def _check(res):
1603             self.failUnlessEqual(res, self._foo_uri)
1604         d.addCallback(_check)
1605         return d
1606 
1607     def test_GET_DIRURL_readonly_uri(self):
1608-        d = self.GET(self.public_url + "/foo?t=readonly-uri")
1609+        d = self.shouldSucceedGET(self.public_url + "/foo?t=readonly-uri")
1610         def _check(res):
1611             self.failUnlessEqual(res, self._foo_readonly_uri)
1612         d.addCallback(_check)
1613         return d
1614 
1615     def test_PUT_NEWDIRURL(self):
1616-        d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1617+        d = self.shouldSucceed("PUT_NEWDIRURL", http.OK, self.PUT,
1618+                               self.public_url + "/foo/newdir?t=mkdir", "")
1619         d.addCallback(lambda res:
1620                       self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1621         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1622@@ -1151,7 +1241,8 @@
1623         return d
1624 
1625     def test_POST_NEWDIRURL(self):
1626-        d = self.POST2(self.public_url + "/foo/newdir?t=mkdir", "")
1627+        d = self.shouldSucceed("POST_NEWDIRURL", http.OK, self.POST2,
1628+                               self.public_url + "/foo/newdir?t=mkdir", "")
1629         d.addCallback(lambda res:
1630                       self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1631         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1632@@ -1160,30 +1251,41 @@
1633 
1634     def test_POST_NEWDIRURL_emptyname(self):
1635         # an empty pathname component (i.e. a double-slash) is disallowed
1636-        d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_emptyname",
1637+        d = self.shouldFail2(error.Error, "POST_NEWDIRURL_emptyname",
1638                              "400 Bad Request",
1639                              "The webapi does not allow empty pathname components, i.e. a double slash",
1640                              self.POST, self.public_url + "//?t=mkdir")
1641         return d
1642 
1643     def test_POST_NEWDIRURL_initial_children(self):
1644-        (newkids, filecap1, filecap2, filecap3,
1645-         dircap) = self._create_initial_children()
1646-        d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-with-children",
1647-                       simplejson.dumps(newkids))
1648+        (newkids, caps) = self._create_initial_children()
1649+        d = self.shouldSucceed("POST_NEWDIRURL_initial_children", http.OK, self.POST2,
1650+                               self.public_url + "/foo/newdir?t=mkdir-with-children",
1651+                               simplejson.dumps(newkids))
1652         def _check(uri):
1653             n = self.s.create_node_from_uri(uri.strip())
1654             d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1655             d2.addCallback(lambda ign:
1656-                           self.failUnlessChildURIIs(n, u"child-imm", filecap1))
1657+                           self.failUnlessROChildURIIs(n, u"child-imm",
1658+                                                       caps['filecap1']))
1659+            d2.addCallback(lambda ign:
1660+                           self.failUnlessRWChildURIIs(n, u"child-mutable",
1661+                                                       caps['filecap2']))
1662+            d2.addCallback(lambda ign:
1663+                           self.failUnlessROChildURIIs(n, u"child-mutable-ro",
1664+                                                       caps['filecap3']))
1665             d2.addCallback(lambda ign:
1666-                           self.failUnlessChildURIIs(n, u"child-mutable",
1667-                                                     filecap2))
1668+                           self.failUnlessROChildURIIs(n, u"unknownchild-ro",
1669+                                                       caps['unknown_rocap']))
1670             d2.addCallback(lambda ign:
1671-                           self.failUnlessChildURIIs(n, u"child-mutable-ro",
1672-                                                     filecap3))
1673+                           self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
1674+                                                       caps['unknown_rwcap']))
1675             d2.addCallback(lambda ign:
1676-                           self.failUnlessChildURIIs(n, u"dirchild", dircap))
1677+                           self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1678+                                                       caps['unknown_immcap']))
1679+            d2.addCallback(lambda ign:
1680+                           self.failUnlessRWChildURIIs(n, u"dirchild",
1681+                                                       caps['dircap']))
1682             return d2
1683         d.addCallback(_check)
1684         d.addCallback(lambda res:
1685@@ -1191,21 +1293,26 @@
1686         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1687         d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1688         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1689-        d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
1690+        d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1691         return d
1692 
1693     def test_POST_NEWDIRURL_immutable(self):
1694-        (newkids, filecap1, immdircap) = self._create_immutable_children()
1695-        d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
1696-                       simplejson.dumps(newkids))
1697+        (newkids, caps) = self._create_immutable_children()
1698+        d = self.shouldSucceed("POST_NEWDIRURL_immutable", http.OK, self.POST2,
1699+                               self.public_url + "/foo/newdir?t=mkdir-immutable",
1700+                               simplejson.dumps(newkids))
1701         def _check(uri):
1702             n = self.s.create_node_from_uri(uri.strip())
1703             d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1704             d2.addCallback(lambda ign:
1705-                           self.failUnlessChildURIIs(n, u"child-imm", filecap1))
1706+                           self.failUnlessROChildURIIs(n, u"child-imm",
1707+                                                       caps['filecap1']))
1708+            d2.addCallback(lambda ign:
1709+                           self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1710+                                                       caps['unknown_immcap']))
1711             d2.addCallback(lambda ign:
1712-                           self.failUnlessChildURIIs(n, u"dirchild-imm",
1713-                                                     immdircap))
1714+                           self.failUnlessROChildURIIs(n, u"dirchild-imm",
1715+                                                       caps['immdircap']))
1716             return d2
1717         d.addCallback(_check)
1718         d.addCallback(lambda res:
1719@@ -1213,25 +1320,27 @@
1720         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1721         d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1722         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1723-        d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
1724+        d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1725+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1726+        d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
1727         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1728-        d.addCallback(self.failUnlessChildURIIs, u"dirchild-imm", immdircap)
1729+        d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
1730         d.addErrback(self.explain_web_error)
1731         return d
1732 
1733     def test_POST_NEWDIRURL_immutable_bad(self):
1734-        (newkids, filecap1, filecap2, filecap3,
1735-         dircap) = self._create_initial_children()
1736-        d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad",
1737+        (newkids, caps) = self._create_initial_children()
1738+        d = self.shouldFail2(error.Error, "POST_NEWDIRURL_immutable_bad",
1739                              "400 Bad Request",
1740-                             "a mkdir-immutable operation was given a child that was not itself immutable",
1741+                             "needed to be immutable but was not",
1742                              self.POST2,
1743                              self.public_url + "/foo/newdir?t=mkdir-immutable",
1744                              simplejson.dumps(newkids))
1745         return d
1746 
1747     def test_PUT_NEWDIRURL_exists(self):
1748-        d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1749+        d = self.shouldSucceed("PUT_NEWDIRURL_exists", http.OK, self.PUT,
1750+                               self.public_url + "/foo/sub?t=mkdir", "")
1751         d.addCallback(lambda res:
1752                       self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1753         d.addCallback(lambda res: self._foo_node.get(u"sub"))
1754@@ -1249,18 +1358,21 @@
1755         d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1756         return d
1757 
1758-    def test_PUT_NEWDIRURL_mkdir_p(self):
1759+    def test_POST_NEWDIRURL_mkdir_p(self):
1760         d = defer.succeed(None)
1761-        d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
1762+        d.addCallback(lambda res: self.shouldSucceed("POST_NEWDIRURL_mkdir_p-1", http.OK, self.POST,
1763+                                                     self.public_url + "/foo", t='mkdir', name='mkp'))
1764         d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
1765         d.addCallback(lambda res: self._foo_node.get(u"mkp"))
1766         def mkdir_p(mkpnode):
1767             url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
1768-            d = self.POST(url)
1769+            d = self.shouldSucceed("POST_NEWDIRURL_mkdir_p-2", http.OK, self.POST,
1770+                                   url)
1771             def made_subsub(ssuri):
1772                 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
1773                 d.addCallback(lambda ssnode: self.failUnlessEqual(ssnode.get_uri(), ssuri))
1774-                d = self.POST(url)
1775+                d = self.shouldSucceed("POST_NEWDIRURL_mkdir_p-3", http.OK, self.POST,
1776+                                       url)
1777                 d.addCallback(lambda uri2: self.failUnlessEqual(uri2, ssuri))
1778                 return d
1779             d.addCallback(made_subsub)
1780@@ -1269,7 +1381,8 @@
1781         return d
1782 
1783     def test_PUT_NEWDIRURL_mkdirs(self):
1784-        d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1785+        d = self.shouldSucceed("PUT_NEWDIRURL_mkdirs", http.OK, self.PUT,
1786+                               self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1787         d.addCallback(lambda res:
1788                       self.failIfNodeHasChild(self._foo_node, u"newdir"))
1789         d.addCallback(lambda res:
1790@@ -1280,21 +1393,22 @@
1791         return d
1792 
1793     def test_DELETE_DIRURL(self):
1794-        d = self.DELETE(self.public_url + "/foo")
1795+        d = self.shouldSucceed("DELETE_DIRURL", http.OK, self.DELETE,
1796+                               self.public_url + "/foo")
1797         d.addCallback(lambda res:
1798                       self.failIfNodeHasChild(self.public_root, u"foo"))
1799         return d
1800 
1801     def test_DELETE_DIRURL_missing(self):
1802         d = self.DELETE(self.public_url + "/foo/missing")
1803-        d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
1804+        d.addBoth(self.should404, "DELETE_DIRURL_missing")
1805         d.addCallback(lambda res:
1806                       self.failUnlessNodeHasChild(self.public_root, u"foo"))
1807         return d
1808 
1809     def test_DELETE_DIRURL_missing2(self):
1810         d = self.DELETE(self.public_url + "/missing")
1811-        d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
1812+        d.addBoth(self.should404, "DELETE_DIRURL_missing2")
1813         return d
1814 
1815     def dump_root(self):
1816@@ -1346,18 +1460,44 @@
1817         d.addCallback(_check)
1818         return d
1819 
1820-    def failUnlessChildURIIs(self, node, name, expected_uri):
1821+    def failUnlessRWChildURIIs(self, node, name, expected_uri):
1822+        assert isinstance(name, unicode)
1823+        d = node.get_child_at_path(name)
1824+        def _check(child):
1825+            self.failUnless(child.is_unknown() or not child.is_readonly())
1826+            self.failUnlessEqual(child.get_uri(), expected_uri.strip())
1827+            expected_ro_uri = self._make_readonly(expected_uri)
1828+            if expected_ro_uri:
1829+                self.failUnlessEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1830+        d.addCallback(_check)
1831+        return d
1832+
1833+    def failUnlessROChildURIIs(self, node, name, expected_uri):
1834         assert isinstance(name, unicode)
1835         d = node.get_child_at_path(name)
1836         def _check(child):
1837+            self.failUnless(child.is_unknown() or child.is_readonly())
1838             self.failUnlessEqual(child.get_uri(), expected_uri.strip())
1839         d.addCallback(_check)
1840         return d
1841 
1842-    def failUnlessURIMatchesChild(self, got_uri, node, name):
1843+    def failUnlessURIMatchesRWChild(self, got_uri, node, name):
1844+        assert isinstance(name, unicode)
1845+        d = node.get_child_at_path(name)
1846+        def _check(child):
1847+            self.failUnless(child.is_unknown() or not child.is_readonly())
1848+            self.failUnlessEqual(child.get_uri(), got_uri.strip())
1849+            expected_ro_uri = self._make_readonly(got_uri)
1850+            if expected_ro_uri:
1851+                self.failUnlessEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1852+        d.addCallback(_check)
1853+        return d
1854+
1855+    def failUnlessURIMatchesROChild(self, got_uri, node, name):
1856         assert isinstance(name, unicode)
1857         d = node.get_child_at_path(name)
1858         def _check(child):
1859+            self.failUnless(child.is_unknown() or child.is_readonly())
1860             self.failUnlessEqual(got_uri.strip(), child.get_uri())
1861         d.addCallback(_check)
1862         return d
1863@@ -1366,10 +1506,11 @@
1864         self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
1865 
1866     def test_POST_upload(self):
1867-        d = self.POST(self.public_url + "/foo", t="upload",
1868-                      file=("new.txt", self.NEWFILE_CONTENTS))
1869+        d = self.shouldSucceed("POST_upload", http.OK, self.POST,
1870+                               self.public_url + "/foo", t="upload",
1871+                               file=("new.txt", self.NEWFILE_CONTENTS))
1872         fn = self._foo_node
1873-        d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1874+        d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
1875         d.addCallback(lambda res:
1876                       self.failUnlessChildContentsAre(fn, u"new.txt",
1877                                                       self.NEWFILE_CONTENTS))
1878@@ -1377,15 +1518,16 @@
1879 
1880     def test_POST_upload_unicode(self):
1881         filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1882-        d = self.POST(self.public_url + "/foo", t="upload",
1883-                      file=(filename, self.NEWFILE_CONTENTS))
1884+        d = self.shouldSucceed("POST_upload_unicode", http.OK, self.POST,
1885+                               self.public_url + "/foo", t="upload",
1886+                               file=(filename, self.NEWFILE_CONTENTS))
1887         fn = self._foo_node
1888-        d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1889+        d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
1890         d.addCallback(lambda res:
1891                       self.failUnlessChildContentsAre(fn, filename,
1892                                                       self.NEWFILE_CONTENTS))
1893         target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1894-        d.addCallback(lambda res: self.GET(target_url))
1895+        d.addCallback(lambda res: self.shouldSucceedGET(target_url))
1896         d.addCallback(lambda contents: self.failUnlessEqual(contents,
1897                                                             self.NEWFILE_CONTENTS,
1898                                                             contents))
1899@@ -1393,24 +1535,26 @@
1900 
1901     def test_POST_upload_unicode_named(self):
1902         filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1903-        d = self.POST(self.public_url + "/foo", t="upload",
1904-                      name=filename,
1905-                      file=("overridden", self.NEWFILE_CONTENTS))
1906+        d = self.shouldSucceed("POST_upload_unicode_named", http.OK, self.POST,
1907+                               self.public_url + "/foo", t="upload",
1908+                               name=filename,
1909+                               file=("overridden", self.NEWFILE_CONTENTS))
1910         fn = self._foo_node
1911-        d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1912+        d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
1913         d.addCallback(lambda res:
1914                       self.failUnlessChildContentsAre(fn, filename,
1915                                                       self.NEWFILE_CONTENTS))
1916         target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1917-        d.addCallback(lambda res: self.GET(target_url))
1918+        d.addCallback(lambda res: self.shouldSucceedGET(target_url))
1919         d.addCallback(lambda contents: self.failUnlessEqual(contents,
1920                                                             self.NEWFILE_CONTENTS,
1921                                                             contents))
1922         return d
1923 
1924     def test_POST_upload_no_link(self):
1925-        d = self.POST("/uri", t="upload",
1926-                      file=("new.txt", self.NEWFILE_CONTENTS))
1927+        d = self.shouldSucceed("POST_upload_no_link", http.OK, self.POST,
1928+                               "/uri", t="upload",
1929+                               file=("new.txt", self.NEWFILE_CONTENTS))
1930         def _check_upload_results(page):
1931             # this should be a page which describes the results of the upload
1932             # that just finished.
1933@@ -1449,7 +1593,7 @@
1934             self.failUnlessEqual(statuscode, str(http.FOUND))
1935             self.failUnless(target.startswith(self.webish_url), target)
1936             return client.getPage(target, method="GET")
1937-        d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
1938+        d = self.shouldRedirect2("POST_upload_no_link_whendone_results",
1939                                  check,
1940                                  self.POST, "/uri", t="upload",
1941                                  when_done="/uri/%(uri)s",
1942@@ -1459,8 +1603,9 @@
1943         return d
1944 
1945     def test_POST_upload_no_link_mutable(self):
1946-        d = self.POST("/uri", t="upload", mutable="true",
1947-                      file=("new.txt", self.NEWFILE_CONTENTS))
1948+        d = self.shouldSucceed("POST_upload_no_link_mutable", http.OK, self.POST,
1949+                               "/uri", t="upload", mutable="true",
1950+                               file=("new.txt", self.NEWFILE_CONTENTS))
1951         def _check(filecap):
1952             filecap = filecap.strip()
1953             self.failUnless(filecap.startswith("URI:SSK:"), filecap)
1954@@ -1472,11 +1617,11 @@
1955         d.addCallback(_check)
1956         def _check2(data):
1957             self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1958-            return self.GET("/uri/%s" % urllib.quote(self.filecap))
1959+            return self.shouldSucceedGET("/uri/%s" % urllib.quote(self.filecap))
1960         d.addCallback(_check2)
1961         def _check3(data):
1962             self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1963-            return self.GET("/file/%s" % urllib.quote(self.filecap))
1964+            return self.shouldSucceedGET("/file/%s" % urllib.quote(self.filecap))
1965         d.addCallback(_check3)
1966         def _check4(data):
1967             self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1968@@ -1485,7 +1630,7 @@
1969 
1970     def test_POST_upload_no_link_mutable_toobig(self):
1971         d = self.shouldFail2(error.Error,
1972-                             "test_POST_upload_no_link_mutable_toobig",
1973+                             "POST_upload_no_link_mutable_toobig",
1974                              "413 Request Entity Too Large",
1975                              "SDMF is limited to one segment, and 10001 > 10000",
1976                              self.POST,
1977@@ -1496,10 +1641,11 @@
1978 
1979     def test_POST_upload_mutable(self):
1980         # this creates a mutable file
1981-        d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
1982-                      file=("new.txt", self.NEWFILE_CONTENTS))
1983+        d = self.shouldSucceed("POST_upload_mutable", http.OK, self.POST,
1984+                               self.public_url + "/foo", t="upload", mutable="true",
1985+                               file=("new.txt", self.NEWFILE_CONTENTS))
1986         fn = self._foo_node
1987-        d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1988+        d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
1989         d.addCallback(lambda res:
1990                       self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1991                                                              self.NEWFILE_CONTENTS))
1992@@ -1515,10 +1661,11 @@
1993         # now upload it again and make sure that the URI doesn't change
1994         NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
1995         d.addCallback(lambda res:
1996-                      self.POST(self.public_url + "/foo", t="upload",
1997-                                mutable="true",
1998-                                file=("new.txt", NEWER_CONTENTS)))
1999-        d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
2000+                      self.shouldSucceed("POST_upload_mutable-again", http.OK, self.POST,
2001+                                         self.public_url + "/foo", t="upload",
2002+                                         mutable="true",
2003+                                         file=("new.txt", NEWER_CONTENTS)))
2004+        d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2005         d.addCallback(lambda res:
2006                       self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2007                                                              NEWER_CONTENTS))
2008@@ -1533,8 +1680,9 @@
2009         # upload a second time, using PUT instead of POST
2010         NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
2011         d.addCallback(lambda res:
2012-                      self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
2013-        d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
2014+                      self.shouldSucceed("POST_upload_mutable-again-with-PUT", http.OK, self.PUT,
2015+                                         self.public_url + "/foo/new.txt", NEW2_CONTENTS))
2016+        d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2017         d.addCallback(lambda res:
2018                       self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2019                                                              NEW2_CONTENTS))
2020@@ -1543,8 +1691,8 @@
2021         # slightly differently
2022 
2023         d.addCallback(lambda res:
2024-                      self.GET(self.public_url + "/foo/",
2025-                               followRedirect=True))
2026+                      self.shouldSucceedGET(self.public_url + "/foo/",
2027+                                            followRedirect=True))
2028         def _check_page(res):
2029             # TODO: assert more about the contents
2030             self.failUnless("SSK" in res)
2031@@ -1561,8 +1709,8 @@
2032 
2033         # look at the JSON form of the enclosing directory
2034         d.addCallback(lambda res:
2035-                      self.GET(self.public_url + "/foo/?t=json",
2036-                               followRedirect=True))
2037+                      self.shouldSucceedGET(self.public_url + "/foo/?t=json",
2038+                                            followRedirect=True))
2039         def _check_page_json(res):
2040             parsed = simplejson.loads(res)
2041             self.failUnlessEqual(parsed[0], "dirnode")
2042@@ -1580,7 +1728,7 @@
2043 
2044         # and the JSON form of the file
2045         d.addCallback(lambda res:
2046-                      self.GET(self.public_url + "/foo/new.txt?t=json"))
2047+                      self.shouldSucceedGET(self.public_url + "/foo/new.txt?t=json"))
2048         def _check_file_json(res):
2049             parsed = simplejson.loads(res)
2050             self.failUnlessEqual(parsed[0], "filenode")
2051@@ -1592,10 +1740,10 @@
2052 
2053         # and look at t=uri and t=readonly-uri
2054         d.addCallback(lambda res:
2055-                      self.GET(self.public_url + "/foo/new.txt?t=uri"))
2056+                      self.shouldSucceedGET(self.public_url + "/foo/new.txt?t=uri"))
2057         d.addCallback(lambda res: self.failUnlessEqual(res, self._mutable_uri))
2058         d.addCallback(lambda res:
2059-                      self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
2060+                      self.shouldSucceedGET(self.public_url + "/foo/new.txt?t=readonly-uri"))
2061         def _check_ro_uri(res):
2062             ro_uri = unicode(self._mutable_node.get_readonly().to_string())
2063             self.failUnlessEqual(res, ro_uri)
2064@@ -1603,15 +1751,15 @@
2065 
2066         # make sure we can get to it from /uri/URI
2067         d.addCallback(lambda res:
2068-                      self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
2069+                      self.shouldSucceedGET("/uri/%s" % urllib.quote(self._mutable_uri)))
2070         d.addCallback(lambda res:
2071                       self.failUnlessEqual(res, NEW2_CONTENTS))
2072 
2073         # and that HEAD computes the size correctly
2074         d.addCallback(lambda res:
2075-                      self.HEAD(self.public_url + "/foo/new.txt",
2076-                                return_response=True))
2077-        def _got_headers((res, status, headers)):
2078+                      self.shouldSucceedHEAD(self.public_url + "/foo/new.txt",
2079+                                             return_response=True))
2080+        def _got_headers((res, statuscode, headers)):
2081             self.failUnlessEqual(res, "")
2082             self.failUnlessEqual(headers["content-length"][0],
2083                                  str(len(NEW2_CONTENTS)))
2084@@ -1621,7 +1769,7 @@
2085         # make sure that size errors are displayed correctly for overwrite
2086         d.addCallback(lambda res:
2087                       self.shouldFail2(error.Error,
2088-                                       "test_POST_upload_mutable-toobig",
2089+                                       "POST_upload_mutable-toobig",
2090                                        "413 Request Entity Too Large",
2091                                        "SDMF is limited to one segment, and 10001 > 10000",
2092                                        self.POST,
2093@@ -1636,7 +1784,7 @@
2094 
2095     def test_POST_upload_mutable_toobig(self):
2096         d = self.shouldFail2(error.Error,
2097-                             "test_POST_upload_mutable_toobig",
2098+                             "POST_upload_mutable_toobig",
2099                              "413 Request Entity Too Large",
2100                              "SDMF is limited to one segment, and 10001 > 10000",
2101                              self.POST,
2102@@ -1660,19 +1808,21 @@
2103         return f
2104 
2105     def test_POST_upload_replace(self):
2106-        d = self.POST(self.public_url + "/foo", t="upload",
2107-                      file=("bar.txt", self.NEWFILE_CONTENTS))
2108+        d = self.shouldSucceed("POST_upload_replace", http.OK, self.POST,
2109+                               self.public_url + "/foo", t="upload",
2110+                               file=("bar.txt", self.NEWFILE_CONTENTS))
2111         fn = self._foo_node
2112-        d.addCallback(self.failUnlessURIMatchesChild, fn, u"bar.txt")
2113+        d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt")
2114         d.addCallback(lambda res:
2115                       self.failUnlessChildContentsAre(fn, u"bar.txt",
2116                                                       self.NEWFILE_CONTENTS))
2117         return d
2118 
2119     def test_POST_upload_no_replace_ok(self):
2120-        d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2121-                      file=("new.txt", self.NEWFILE_CONTENTS))
2122-        d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
2123+        d = self.shouldSucceed("POST_upload_no_replace_ok", http.OK, self.POST,
2124+                               self.public_url + "/foo?replace=false", t="upload",
2125+                               file=("new.txt", self.NEWFILE_CONTENTS))
2126+        d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/new.txt"))
2127         d.addCallback(lambda res: self.failUnlessEqual(res,
2128                                                        self.NEWFILE_CONTENTS))
2129         return d
2130@@ -1685,7 +1835,7 @@
2131                   "409 Conflict",
2132                   "There was already a child by that name, and you asked me "
2133                   "to not replace it")
2134-        d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2135+        d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/bar.txt"))
2136         d.addCallback(self.failUnlessIsBarDotTxt)
2137         return d
2138 
2139@@ -1696,7 +1846,7 @@
2140                   "409 Conflict",
2141                   "There was already a child by that name, and you asked me "
2142                   "to not replace it")
2143-        d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2144+        d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/bar.txt"))
2145         d.addCallback(self.failUnlessIsBarDotTxt)
2146         return d
2147 
2148@@ -1712,9 +1862,10 @@
2149 
2150     def test_POST_upload_named(self):
2151         fn = self._foo_node
2152-        d = self.POST(self.public_url + "/foo", t="upload",
2153-                      name="new.txt", file=self.NEWFILE_CONTENTS)
2154-        d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
2155+        d = self.shouldSucceed("POST_upload_named", http.OK, self.POST,
2156+                               self.public_url + "/foo", t="upload",
2157+                               name="new.txt", file=self.NEWFILE_CONTENTS)
2158+        d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
2159         d.addCallback(lambda res:
2160                       self.failUnlessChildContentsAre(fn, u"new.txt",
2161                                                       self.NEWFILE_CONTENTS))
2162@@ -1724,7 +1875,7 @@
2163         d = self.POST(self.public_url + "/foo", t="upload",
2164                       name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
2165         d.addBoth(self.shouldFail, error.Error,
2166-                  "test_POST_upload_named_badfilename",
2167+                  "POST_upload_named_badfilename",
2168                   "400 Bad Request",
2169                   "name= may not contain a slash",
2170                   )
2171@@ -1738,7 +1889,8 @@
2172 
2173     def test_POST_FILEURL_check(self):
2174         bar_url = self.public_url + "/foo/bar.txt"
2175-        d = self.POST(bar_url, t="check")
2176+        d = self.shouldSucceed("POST_FILEURL_check-1", http.OK, self.POST,
2177+                               bar_url, t="check")
2178         def _check(res):
2179             self.failUnless("Healthy :" in res)
2180         d.addCallback(_check)
2181@@ -1747,13 +1899,14 @@
2182             self.failUnlessEqual(statuscode, str(http.FOUND))
2183             self.failUnlessEqual(target, redir_url)
2184         d.addCallback(lambda res:
2185-                      self.shouldRedirect2("test_POST_FILEURL_check",
2186+                      self.shouldRedirect2("POST_FILEURL_check-2",
2187                                            _check2,
2188                                            self.POST, bar_url,
2189                                            t="check",
2190                                            when_done=redir_url))
2191         d.addCallback(lambda res:
2192-                      self.POST(bar_url, t="check", return_to=redir_url))
2193+                      self.shouldSucceed("POST_FILEURL_check-3", http.OK, self.POST,
2194+                                         bar_url, t="check", return_to=redir_url))
2195         def _check3(res):
2196             self.failUnless("Healthy :" in res)
2197             self.failUnless("Return to file" in res)
2198@@ -1761,7 +1914,8 @@
2199         d.addCallback(_check3)
2200 
2201         d.addCallback(lambda res:
2202-                      self.POST(bar_url, t="check", output="JSON"))
2203+                      self.shouldSucceed("POST_FILEURL_check-4", http.OK, self.POST,
2204+                                         bar_url, t="check", output="JSON"))
2205         def _check_json(res):
2206             data = simplejson.loads(res)
2207             self.failUnless("storage-index" in data)
2208@@ -1772,7 +1926,8 @@
2209 
2210     def test_POST_FILEURL_check_and_repair(self):
2211         bar_url = self.public_url + "/foo/bar.txt"
2212-        d = self.POST(bar_url, t="check", repair="true")
2213+        d = self.shouldSucceed("POST_FILEURL_check_and_repair-1", http.OK, self.POST,
2214+                               bar_url, t="check", repair="true")
2215         def _check(res):
2216             self.failUnless("Healthy :" in res)
2217         d.addCallback(_check)
2218@@ -1781,13 +1936,14 @@
2219             self.failUnlessEqual(statuscode, str(http.FOUND))
2220             self.failUnlessEqual(target, redir_url)
2221         d.addCallback(lambda res:
2222-                      self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
2223+                      self.shouldRedirect2("POST_FILEURL_check_and_repair-2",
2224                                            _check2,
2225                                            self.POST, bar_url,
2226                                            t="check", repair="true",
2227                                            when_done=redir_url))
2228         d.addCallback(lambda res:
2229-                      self.POST(bar_url, t="check", return_to=redir_url))
2230+                      self.shouldSucceed("POST_FILEURL_check_and_repair-3", http.OK, self.POST,
2231+                                         bar_url, t="check", return_to=redir_url))
2232         def _check3(res):
2233             self.failUnless("Healthy :" in res)
2234             self.failUnless("Return to file" in res)
2235@@ -1797,7 +1953,8 @@
2236 
2237     def test_POST_DIRURL_check(self):
2238         foo_url = self.public_url + "/foo/"
2239-        d = self.POST(foo_url, t="check")
2240+        d = self.shouldSucceed("POST_DIRURL_check-1", http.OK, self.POST,
2241+                               foo_url, t="check")
2242         def _check(res):
2243             self.failUnless("Healthy :" in res, res)
2244         d.addCallback(_check)
2245@@ -1806,13 +1963,14 @@
2246             self.failUnlessEqual(statuscode, str(http.FOUND))
2247             self.failUnlessEqual(target, redir_url)
2248         d.addCallback(lambda res:
2249-                      self.shouldRedirect2("test_POST_DIRURL_check",
2250+                      self.shouldRedirect2("POST_DIRURL_check-2",
2251                                            _check2,
2252                                            self.POST, foo_url,
2253                                            t="check",
2254                                            when_done=redir_url))
2255         d.addCallback(lambda res:
2256-                      self.POST(foo_url, t="check", return_to=redir_url))
2257+                      self.shouldSucceed("POST_DIRURL_check-3", http.OK, self.POST,
2258+                                         foo_url, t="check", return_to=redir_url))
2259         def _check3(res):
2260             self.failUnless("Healthy :" in res, res)
2261             self.failUnless("Return to file/directory" in res)
2262@@ -1820,7 +1978,8 @@
2263         d.addCallback(_check3)
2264 
2265         d.addCallback(lambda res:
2266-                      self.POST(foo_url, t="check", output="JSON"))
2267+                      self.shouldSucceed("POST_DIRURL_check-4", http.OK, self.POST,
2268+                                         foo_url, t="check", output="JSON"))
2269         def _check_json(res):
2270             data = simplejson.loads(res)
2271             self.failUnless("storage-index" in data)
2272@@ -1831,7 +1990,8 @@
2273 
2274     def test_POST_DIRURL_check_and_repair(self):
2275         foo_url = self.public_url + "/foo/"
2276-        d = self.POST(foo_url, t="check", repair="true")
2277+        d = self.shouldSucceed("POST_DIRURL_check_and_repair-1", http.OK, self.POST,
2278+                               foo_url, t="check", repair="true")
2279         def _check(res):
2280             self.failUnless("Healthy :" in res, res)
2281         d.addCallback(_check)
2282@@ -1840,13 +2000,14 @@
2283             self.failUnlessEqual(statuscode, str(http.FOUND))
2284             self.failUnlessEqual(target, redir_url)
2285         d.addCallback(lambda res:
2286-                      self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
2287+                      self.shouldRedirect2("POST_DIRURL_check_and_repair-2",
2288                                            _check2,
2289                                            self.POST, foo_url,
2290                                            t="check", repair="true",
2291                                            when_done=redir_url))
2292         d.addCallback(lambda res:
2293-                      self.POST(foo_url, t="check", return_to=redir_url))
2294+                      self.shouldSucceed("POST_DIRURL_check_and_repair-3", http.OK, self.POST,
2295+                                         foo_url, t="check", return_to=redir_url))
2296         def _check3(res):
2297             self.failUnless("Healthy :" in res)
2298             self.failUnless("Return to file/directory" in res)
2299@@ -1857,7 +2018,7 @@
2300     def wait_for_operation(self, ignored, ophandle):
2301         url = "/operations/" + ophandle
2302         url += "?t=status&output=JSON"
2303-        d = self.GET(url)
2304+        d = self.shouldSucceedGET(url)
2305         def _got(res):
2306             data = simplejson.loads(res)
2307             if not data["finished"]:
2308@@ -1873,7 +2034,7 @@
2309         url += "?t=status"
2310         if output:
2311             url += "&output=" + output
2312-        d = self.GET(url)
2313+        d = self.shouldSucceedGET(url)
2314         def _got(res):
2315             if output and output.lower() == "json":
2316                 return simplejson.loads(res)
2317@@ -1883,7 +2044,7 @@
2318 
2319     def test_POST_DIRURL_deepcheck_no_ophandle(self):
2320         d = self.shouldFail2(error.Error,
2321-                             "test_POST_DIRURL_deepcheck_no_ophandle",
2322+                             "POST_DIRURL_deepcheck_no_ophandle",
2323                              "400 Bad Request",
2324                              "slow operation requires ophandle=",
2325                              self.POST, self.public_url, t="start-deep-check")
2326@@ -1893,7 +2054,7 @@
2327         def _check_redirect(statuscode, target):
2328             self.failUnlessEqual(statuscode, str(http.FOUND))
2329             self.failUnless(target.endswith("/operations/123"))
2330-        d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
2331+        d = self.shouldRedirect2("POST_DIRURL_deepcheck", _check_redirect,
2332                                  self.POST, self.public_url,
2333                                  t="start-deep-check", ophandle="123")
2334         d.addCallback(self.wait_for_operation, "123")
2335@@ -1909,7 +2070,7 @@
2336         d.addCallback(_check_html)
2337 
2338         d.addCallback(lambda res:
2339-                      self.GET("/operations/123/"))
2340+                      self.shouldSucceedGET("/operations/123/"))
2341         d.addCallback(_check_html) # should be the same as without the slash
2342 
2343         d.addCallback(lambda res:
2344@@ -1920,7 +2081,7 @@
2345         foo_si = self._foo_node.get_storage_index()
2346         foo_si_s = base32.b2a(foo_si)
2347         d.addCallback(lambda res:
2348-                      self.GET("/operations/123/%s?output=JSON" % foo_si_s))
2349+                      self.shouldSucceedGET("/operations/123/%s?output=JSON" % foo_si_s))
2350         def _check_foo_json(res):
2351             data = simplejson.loads(res)
2352             self.failUnlessEqual(data["storage-index"], foo_si_s)
2353@@ -1929,8 +2090,9 @@
2354         return d
2355 
2356     def test_POST_DIRURL_deepcheck_and_repair(self):
2357-        d = self.POST(self.public_url, t="start-deep-check", repair="true",
2358-                      ophandle="124", output="json", followRedirect=True)
2359+        d = self.shouldSucceed("POST_DIRURL_deepcheck_and_repair", http.OK, self.POST,
2360+                               self.public_url, t="start-deep-check", repair="true",
2361+                               ophandle="124", output="json", followRedirect=True)
2362         d.addCallback(self.wait_for_operation, "124")
2363         def _check_json(data):
2364             self.failUnlessEqual(data["finished"], True)
2365@@ -1971,45 +2133,47 @@
2366         return d
2367 
2368     def test_POST_mkdir(self): # return value?
2369-        d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
2370+        d = self.shouldSucceed("POST_mkdir", http.OK, self.POST,
2371+                               self.public_url + "/foo", t="mkdir", name="newdir")
2372         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2373         d.addCallback(self.failUnlessNodeKeysAre, [])
2374         return d
2375 
2376     def test_POST_mkdir_initial_children(self):
2377-        newkids, filecap1, ign, ign, ign = self._create_initial_children()
2378-        d = self.POST2(self.public_url +
2379-                       "/foo?t=mkdir-with-children&name=newdir",
2380-                       simplejson.dumps(newkids))
2381+        (newkids, caps) = self._create_initial_children()
2382+        d = self.shouldSucceed("POST_mkdir_initial_children", http.OK, self.POST2,
2383+                               self.public_url + "/foo?t=mkdir-with-children&name=newdir",
2384+                               simplejson.dumps(newkids))
2385         d.addCallback(lambda res:
2386                       self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2387         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2388         d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2389         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2390-        d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
2391+        d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2392         return d
2393 
2394     def test_POST_mkdir_immutable(self):
2395-        (newkids, filecap1, immdircap) = self._create_immutable_children()
2396-        d = self.POST2(self.public_url +
2397-                       "/foo?t=mkdir-immutable&name=newdir",
2398-                       simplejson.dumps(newkids))
2399+        (newkids, caps) = self._create_immutable_children()
2400+        d = self.shouldSucceed("POST_mkdir_immutable", http.OK, self.POST2,
2401+                               self.public_url + "/foo?t=mkdir-immutable&name=newdir",
2402+                               simplejson.dumps(newkids))
2403         d.addCallback(lambda res:
2404                       self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2405         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2406         d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2407         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2408-        d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
2409+        d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2410+        d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2411+        d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
2412         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2413-        d.addCallback(self.failUnlessChildURIIs, u"dirchild-imm", immdircap)
2414+        d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
2415         return d
2416 
2417     def test_POST_mkdir_immutable_bad(self):
2418-        (newkids, filecap1, filecap2, filecap3,
2419-         dircap) = self._create_initial_children()
2420-        d = self.shouldFail2(error.Error, "test_POST_mkdir_immutable_bad",
2421+        (newkids, caps) = self._create_initial_children()
2422+        d = self.shouldFail2(error.Error, "POST_mkdir_immutable_bad",
2423                              "400 Bad Request",
2424-                             "a mkdir-immutable operation was given a child that was not itself immutable",
2425+                             "needed to be immutable but was not",
2426                              self.POST2,
2427                              self.public_url +
2428                              "/foo?t=mkdir-immutable&name=newdir",
2429@@ -2017,7 +2181,8 @@
2430         return d
2431 
2432     def test_POST_mkdir_2(self):
2433-        d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
2434+        d = self.shouldSucceed("POST_mkdir_2", http.OK, self.POST,
2435+                               self.public_url + "/foo/newdir?t=mkdir", "")
2436         d.addCallback(lambda res:
2437                       self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2438         d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2439@@ -2025,7 +2190,8 @@
2440         return d
2441 
2442     def test_POST_mkdirs_2(self):
2443-        d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
2444+        d = self.shouldSucceed("POST_mkdirs_2", http.OK, self.POST,
2445+                               self.public_url + "/foo/bardir/newdir?t=mkdir", "")
2446         d.addCallback(lambda res:
2447                       self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
2448         d.addCallback(lambda res: self._foo_node.get(u"bardir"))
2449@@ -2034,7 +2200,8 @@
2450         return d
2451 
2452     def test_POST_mkdir_no_parentdir_noredirect(self):
2453-        d = self.POST("/uri?t=mkdir")
2454+        d = self.shouldSucceed("POST_mkdir_no_parentdir_noredirect", http.OK, self.POST,
2455+                               "/uri?t=mkdir")
2456         def _after_mkdir(res):
2457             uri.DirectoryURI.init_from_string(res)
2458         d.addCallback(_after_mkdir)
2459@@ -2049,21 +2216,43 @@
2460         d.addCallback(_check_target)
2461         return d
2462 
2463+    def _make_readonly(self, u):
2464+        ro_uri = uri.from_string(u).get_readonly()
2465+        if ro_uri is None:
2466+            return None
2467+        return ro_uri.to_string()
2468+
2469     def _create_initial_children(self):
2470         contents, n, filecap1 = self.makefile(12)
2471         md1 = {"metakey1": "metavalue1"}
2472         filecap2 = make_mutable_file_uri()
2473         node3 = self.s.create_node_from_uri(make_mutable_file_uri())
2474         filecap3 = node3.get_readonly_uri()
2475+        unknown_rwcap = "lafs://from_the_future"
2476+        unknown_rocap = "ro.lafs://readonly_from_the_future"
2477+        unknown_immcap = "imm.lafs://immutable_from_the_future"
2478         node4 = self.s.create_node_from_uri(make_mutable_file_uri())
2479         dircap = DirectoryNode(node4, None, None).get_uri()
2480-        newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
2481-                                               "metadata": md1, }],
2482-                   u"child-mutable": ["filenode", {"rw_uri": filecap2}],
2483+        newkids = {u"child-imm":        ["filenode", {"rw_uri": filecap1,
2484+                                                      "ro_uri": self._make_readonly(filecap1),
2485+                                                      "metadata": md1, }],
2486+                   u"child-mutable":    ["filenode", {"rw_uri": filecap2,
2487+                                                      "ro_uri": self._make_readonly(filecap2)}],
2488                    u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}],
2489-                   u"dirchild": ["dirnode", {"rw_uri": dircap}],
2490+                   u"unknownchild-rw":  ["unknown",  {"rw_uri": unknown_rwcap,
2491+                                                      "ro_uri": unknown_rocap}],
2492+                   u"unknownchild-ro":  ["unknown",  {"ro_uri": unknown_rocap}],
2493+                   u"unknownchild-imm": ["unknown",  {"ro_uri": unknown_immcap}],
2494+                   u"dirchild":         ["dirnode",  {"rw_uri": dircap,
2495+                                                      "ro_uri": self._make_readonly(dircap)}],
2496                    }
2497-        return newkids, filecap1, filecap2, filecap3, dircap
2498+        return newkids, {'filecap1': filecap1,
2499+                         'filecap2': filecap2,
2500+                         'filecap3': filecap3,
2501+                         'unknown_rwcap': unknown_rwcap,
2502+                         'unknown_rocap': unknown_rocap,
2503+                         'unknown_immcap': unknown_immcap,
2504+                         'dircap': dircap}
2505 
2506     def _create_immutable_children(self):
2507         contents, n, filecap1 = self.makefile(12)
2508@@ -2071,31 +2260,46 @@
2509         tnode = create_chk_filenode("immutable directory contents\n"*10)
2510         dnode = DirectoryNode(tnode, None, None)
2511         assert not dnode.is_mutable()
2512+        unknown_immcap = "imm.lafs://immutable_from_the_future"
2513         immdircap = dnode.get_uri()
2514-        newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
2515-                                               "metadata": md1, }],
2516-                   u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
2517+        newkids = {u"child-imm":        ["filenode", {"ro_uri": filecap1,
2518+                                                      "metadata": md1, }],
2519+                   u"unknownchild-imm": ["unknown",  {"ro_uri": unknown_immcap}],
2520+                   u"dirchild-imm":     ["dirnode",  {"ro_uri": immdircap}],
2521                    }
2522-        return newkids, filecap1, immdircap
2523+        return newkids, {'filecap1': filecap1,
2524+                         'unknown_immcap': unknown_immcap,
2525+                         'immdircap': immdircap}
2526 
2527     def test_POST_mkdir_no_parentdir_initial_children(self):
2528-        (newkids, filecap1, filecap2, filecap3,
2529-         dircap) = self._create_initial_children()
2530-        d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
2531+        (newkids, caps) = self._create_initial_children()
2532+        d = self.shouldSucceed("POST_mkdir_no_parentdir_initial_children", http.OK, self.POST2,
2533+                               "/uri?t=mkdir-with-children", simplejson.dumps(newkids))
2534         def _after_mkdir(res):
2535             self.failUnless(res.startswith("URI:DIR"), res)
2536             n = self.s.create_node_from_uri(res)
2537             d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2538             d2.addCallback(lambda ign:
2539-                           self.failUnlessChildURIIs(n, u"child-imm", filecap1))
2540+                           self.failUnlessROChildURIIs(n, u"child-imm",
2541+                                                       caps['filecap1']))
2542+            d2.addCallback(lambda ign:
2543+                           self.failUnlessRWChildURIIs(n, u"child-mutable",
2544+                                                       caps['filecap2']))
2545+            d2.addCallback(lambda ign:
2546+                           self.failUnlessROChildURIIs(n, u"child-mutable-ro",
2547+                                                       caps['filecap3']))
2548             d2.addCallback(lambda ign:
2549-                           self.failUnlessChildURIIs(n, u"child-mutable",
2550-                                                     filecap2))
2551+                           self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
2552+                                                       caps['unknown_rwcap']))
2553             d2.addCallback(lambda ign:
2554-                           self.failUnlessChildURIIs(n, u"child-mutable-ro",
2555-                                                     filecap3))
2556+                           self.failUnlessROChildURIIs(n, u"unknownchild-ro",
2557+                                                       caps['unknown_rocap']))
2558             d2.addCallback(lambda ign:
2559-                           self.failUnlessChildURIIs(n, u"dirchild", dircap))
2560+                           self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2561+                                                       caps['unknown_immcap']))
2562+            d2.addCallback(lambda ign:
2563+                           self.failUnlessRWChildURIIs(n, u"dirchild",
2564+                                                       caps['dircap']))
2565             return d2
2566         d.addCallback(_after_mkdir)
2567         return d
2568@@ -2103,8 +2307,7 @@
2569     def test_POST_mkdir_no_parentdir_unexpected_children(self):
2570         # the regular /uri?t=mkdir operation is specified to ignore its body.
2571         # Only t=mkdir-with-children pays attention to it.
2572-        (newkids, filecap1, filecap2, filecap3,
2573-         dircap) = self._create_initial_children()
2574+        (newkids, caps) = self._create_initial_children()
2575         d = self.shouldHTTPError("POST t=mkdir unexpected children",
2576                                  400, "Bad Request",
2577                                  "t=mkdir does not accept children=, "
2578@@ -2121,28 +2324,32 @@
2579         return d
2580 
2581     def test_POST_mkdir_no_parentdir_immutable(self):
2582-        (newkids, filecap1, immdircap) = self._create_immutable_children()
2583-        d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
2584+        (newkids, caps) = self._create_immutable_children()
2585+        d = self.shouldSucceed("POST_mkdir_no_parentdir_immutable", http.OK, self.POST2,
2586+                               "/uri?t=mkdir-immutable", simplejson.dumps(newkids))
2587         def _after_mkdir(res):
2588             self.failUnless(res.startswith("URI:DIR"), res)
2589             n = self.s.create_node_from_uri(res)
2590             d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2591             d2.addCallback(lambda ign:
2592-                           self.failUnlessChildURIIs(n, u"child-imm", filecap1))
2593+                           self.failUnlessROChildURIIs(n, u"child-imm",
2594+                                                          caps['filecap1']))
2595+            d2.addCallback(lambda ign:
2596+                           self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2597+                                                          caps['unknown_immcap']))
2598             d2.addCallback(lambda ign:
2599-                           self.failUnlessChildURIIs(n, u"dirchild-imm",
2600-                                                     immdircap))
2601+                           self.failUnlessROChildURIIs(n, u"dirchild-imm",
2602+                                                          caps['immdircap']))
2603             return d2
2604         d.addCallback(_after_mkdir)
2605         return d
2606 
2607     def test_POST_mkdir_no_parentdir_immutable_bad(self):
2608-        (newkids, filecap1, filecap2, filecap3,
2609-         dircap) = self._create_initial_children()
2610+        (newkids, caps) = self._create_initial_children()
2611         d = self.shouldFail2(error.Error,
2612-                             "test_POST_mkdir_no_parentdir_immutable_bad",
2613+                             "POST_mkdir_no_parentdir_immutable_bad",
2614                              "400 Bad Request",
2615-                             "a mkdir-immutable operation was given a child that was not itself immutable",
2616+                             "needed to be immutable but was not",
2617                              self.POST2,
2618                              "/uri?t=mkdir-immutable",
2619                              simplejson.dumps(newkids))
2620@@ -2150,9 +2357,14 @@
2621 
2622     def test_welcome_page_mkdir_button(self):
2623         # Fetch the welcome page.
2624-        d = self.GET("/")
2625+        d = self.shouldSucceedGET("/")
2626         def _after_get_welcome_page(res):
2627-            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 a directory" />', re.I)
2628+            MKDIR_BUTTON_RE = re.compile(
2629+                '<form action="([^"]*)" method="post".*?'
2630+                '<input type="hidden" name="t" value="([^"]*)" />'
2631+                '<input type="hidden" name="([^"]*)" value="([^"]*)" />'
2632+                '<input type="submit" value="Create a directory" />',
2633+                re.I)
2634             mo = MKDIR_BUTTON_RE.search(res)
2635             formaction = mo.group(1)
2636             formt = mo.group(2)
2637@@ -2168,7 +2380,8 @@
2638         return d
2639 
2640     def test_POST_mkdir_replace(self): # return value?
2641-        d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
2642+        d = self.shouldSucceed("POST_mkdir_replace", http.OK, self.POST,
2643+                               self.public_url + "/foo", t="mkdir", name="sub")
2644         d.addCallback(lambda res: self._foo_node.get(u"sub"))
2645         d.addCallback(self.failUnlessNodeKeysAre, [])
2646         return d
2647@@ -2250,9 +2463,9 @@
2648 
2649         d = client.getPage(url, method="POST", postdata=reqbody)
2650         def _then(res):
2651-            self.failUnlessURIMatchesChild(newuri9, self._foo_node, u"atomic_added_1")
2652-            self.failUnlessURIMatchesChild(newuri10, self._foo_node, u"atomic_added_2")
2653-            self.failUnlessURIMatchesChild(newuri11, self._foo_node, u"atomic_added_3")
2654+            self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1")
2655+            self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2")
2656+            self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3")
2657 
2658         d.addCallback(_then)
2659         d.addErrback(self.dump_error)
2660@@ -2260,8 +2473,9 @@
2661 
2662     def test_POST_put_uri(self):
2663         contents, n, newuri = self.makefile(8)
2664-        d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
2665-        d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
2666+        d = self.shouldSucceed("POST_put_uri", http.OK, self.POST,
2667+                               self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
2668+        d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
2669         d.addCallback(lambda res:
2670                       self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
2671                                                       contents))
2672@@ -2269,8 +2483,9 @@
2673 
2674     def test_POST_put_uri_replace(self):
2675         contents, n, newuri = self.makefile(8)
2676-        d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
2677-        d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
2678+        d = self.shouldSucceed("POST_put_uri_replace", http.OK, self.POST,
2679+                               self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
2680+        d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
2681         d.addCallback(lambda res:
2682                       self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
2683                                                       contents))
2684@@ -2285,7 +2500,7 @@
2685                   "409 Conflict",
2686                   "There was already a child by that name, and you asked me "
2687                   "to not replace it")
2688-        d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2689+        d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/bar.txt"))
2690         d.addCallback(self.failUnlessIsBarDotTxt)
2691         return d
2692 
2693@@ -2298,12 +2513,13 @@
2694                   "409 Conflict",
2695                   "There was already a child by that name, and you asked me "
2696                   "to not replace it")
2697-        d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2698+        d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/bar.txt"))
2699         d.addCallback(self.failUnlessIsBarDotTxt)
2700         return d
2701 
2702     def test_POST_delete(self):
2703-        d = self.POST(self.public_url + "/foo", t="delete", name="bar.txt")
2704+        d = self.shouldSucceed("POST_delete", http.OK, self.POST,
2705+                               self.public_url + "/foo", t="delete", name="bar.txt")
2706         d.addCallback(lambda res: self._foo_node.list())
2707         def _check(children):
2708             self.failIf(u"bar.txt" in children)
2709@@ -2311,40 +2527,43 @@
2710         return d
2711 
2712     def test_POST_rename_file(self):
2713-        d = self.POST(self.public_url + "/foo", t="rename",
2714-                      from_name="bar.txt", to_name='wibble.txt')
2715+        d = self.shouldSucceed("POST_rename_file", http.OK, self.POST,
2716+                               self.public_url + "/foo", t="rename",
2717+                               from_name="bar.txt", to_name='wibble.txt')
2718         d.addCallback(lambda res:
2719                       self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
2720         d.addCallback(lambda res:
2721                       self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
2722-        d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
2723+        d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/wibble.txt"))
2724         d.addCallback(self.failUnlessIsBarDotTxt)
2725-        d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
2726+        d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/wibble.txt?t=json"))
2727         d.addCallback(self.failUnlessIsBarJSON)
2728         return d
2729 
2730     def test_POST_rename_file_redundant(self):
2731-        d = self.POST(self.public_url + "/foo", t="rename",
2732-                      from_name="bar.txt", to_name='bar.txt')
2733+        d = self.shouldSucceed("POST_rename_file_redundant", http.OK, self.POST,
2734+                               self.public_url + "/foo", t="rename",
2735+                               from_name="bar.txt", to_name='bar.txt')
2736         d.addCallback(lambda res:
2737                       self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2738-        d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2739+        d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/bar.txt"))
2740         d.addCallback(self.failUnlessIsBarDotTxt)
2741-        d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
2742+        d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/bar.txt?t=json"))
2743         d.addCallback(self.failUnlessIsBarJSON)
2744         return d
2745 
2746     def test_POST_rename_file_replace(self):
2747         # rename a file and replace a directory with it
2748-        d = self.POST(self.public_url + "/foo", t="rename",
2749-                      from_name="bar.txt", to_name='empty')
2750+        d = self.shouldSucceed("POST_rename_file_replace", http.OK, self.POST,
2751+                               self.public_url + "/foo", t="rename",
2752+                               from_name="bar.txt", to_name='empty')
2753         d.addCallback(lambda res:
2754                       self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
2755         d.addCallback(lambda res:
2756                       self.failUnlessNodeHasChild(self._foo_node, u"empty"))
2757-        d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
2758+        d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/empty"))
2759         d.addCallback(self.failUnlessIsBarDotTxt)
2760-        d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2761+        d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/empty?t=json"))
2762         d.addCallback(self.failUnlessIsBarJSON)
2763         return d
2764 
2765@@ -2357,7 +2576,7 @@
2766                   "409 Conflict",
2767                   "There was already a child by that name, and you asked me "
2768                   "to not replace it")
2769-        d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2770+        d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/empty?t=json"))
2771         d.addCallback(self.failUnlessIsEmptyJSON)
2772         return d
2773 
2774@@ -2370,7 +2589,7 @@
2775                   "409 Conflict",
2776                   "There was already a child by that name, and you asked me "
2777                   "to not replace it")
2778-        d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2779+        d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/empty?t=json"))
2780         d.addCallback(self.failUnlessIsEmptyJSON)
2781         return d
2782 
2783@@ -2383,7 +2602,7 @@
2784         d = self.POST(self.public_url + "/foo", t="rename",
2785                       from_name="bar.txt", to_name='kirk/spock.txt')
2786         d.addBoth(self.shouldFail, error.Error,
2787-                  "test_POST_rename_file_slash_fail",
2788+                  "POST_rename_file_slash_fail",
2789                   "400 Bad Request",
2790                   "to_name= may not contain a slash",
2791                   )
2792@@ -2392,13 +2611,14 @@
2793         return d
2794 
2795     def test_POST_rename_dir(self):
2796-        d = self.POST(self.public_url, t="rename",
2797-                      from_name="foo", to_name='plunk')
2798+        d = self.shouldSucceed("POST_rename_dir", http.OK, self.POST,
2799+                               self.public_url, t="rename",
2800+                               from_name="foo", to_name='plunk')
2801         d.addCallback(lambda res:
2802                       self.failIfNodeHasChild(self.public_root, u"foo"))
2803         d.addCallback(lambda res:
2804                       self.failUnlessNodeHasChild(self.public_root, u"plunk"))
2805-        d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
2806+        d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/plunk?t=json"))
2807         d.addCallback(self.failUnlessIsFooJSON)
2808         return d
2809 
2810@@ -2433,24 +2653,24 @@
2811         d.addCallback(lambda res: self.GET(base+"&t=json"))
2812         d.addBoth(self.shouldRedirect, targetbase+"?t=json")
2813         d.addCallback(self.log, "about to get file by uri")
2814-        d.addCallback(lambda res: self.GET(base, followRedirect=True))
2815+        d.addCallback(lambda res: self.shouldSucceedGET(base, followRedirect=True))
2816         d.addCallback(self.failUnlessIsBarDotTxt)
2817         d.addCallback(self.log, "got file by uri, about to get dir by uri")
2818-        d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
2819-                                           followRedirect=True))
2820+        d.addCallback(lambda res: self.shouldSucceedGET("/uri?uri=%s&t=json" % self._foo_uri,
2821+                                                        followRedirect=True))
2822         d.addCallback(self.failUnlessIsFooJSON)
2823         d.addCallback(self.log, "got dir by uri")
2824 
2825         return d
2826 
2827     def test_GET_URI_form_bad(self):
2828-        d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
2829+        d = self.shouldFail2(error.Error, "GET_URI_form_bad",
2830                              "400 Bad Request", "GET /uri requires uri=",
2831                              self.GET, "/uri")
2832         return d
2833 
2834     def test_GET_rename_form(self):
2835-        d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
2836+        d = self.shouldSucceedGET(self.public_url + "/foo?t=rename-form&name=bar.txt",
2837                      followRedirect=True)
2838         def _check(res):
2839             self.failUnless('name="when_done" value="."' in res, res)
2840@@ -2465,23 +2685,23 @@
2841 
2842     def test_GET_URI_URL(self):
2843         base = "/uri/%s" % self._bar_txt_uri
2844-        d = self.GET(base)
2845+        d = self.shouldSucceedGET(base)
2846         d.addCallback(self.failUnlessIsBarDotTxt)
2847-        d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
2848+        d.addCallback(lambda res: self.shouldSucceedGET(base+"?filename=bar.txt"))
2849         d.addCallback(self.failUnlessIsBarDotTxt)
2850-        d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
2851+        d.addCallback(lambda res: self.shouldSucceedGET(base+"?filename=bar.txt&save=true"))
2852         d.addCallback(self.failUnlessIsBarDotTxt)
2853         return d
2854 
2855     def test_GET_URI_URL_dir(self):
2856         base = "/uri/%s?t=json" % self._foo_uri
2857-        d = self.GET(base)
2858+        d = self.shouldSucceedGET(base)
2859         d.addCallback(self.failUnlessIsFooJSON)
2860         return d
2861 
2862     def test_GET_URI_URL_missing(self):
2863         base = "/uri/%s" % self._bad_file_uri
2864-        d = self.shouldHTTPError("test_GET_URI_URL_missing",
2865+        d = self.shouldHTTPError("GET_URI_URL_missing",
2866                                  http.GONE, None, "NotEnoughSharesError",
2867                                  self.GET, base)
2868         # TODO: how can we exercise both sides of WebDownloadTarget.fail
2869@@ -2499,9 +2719,9 @@
2870             d.addCallback(lambda res:
2871                           self.failUnlessEqual(res.strip(), new_uri))
2872             d.addCallback(lambda res:
2873-                          self.failUnlessChildURIIs(self.public_root,
2874-                                                    u"foo",
2875-                                                    new_uri))
2876+                          self.failUnlessRWChildURIIs(self.public_root,
2877+                                                      u"foo",
2878+                                                      new_uri))
2879             return d
2880         d.addCallback(_made_dir)
2881         return d
2882@@ -2512,32 +2732,33 @@
2883             new_uri = dn.get_uri()
2884             # replace /foo with a new (empty) directory, but ask that
2885             # replace=false, so it should fail
2886-            d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
2887+            d = self.shouldFail2(error.Error, "PUT_DIRURL_uri_noreplace",
2888                                  "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
2889                                  self.PUT,
2890                                  self.public_url + "/foo?t=uri&replace=false",
2891                                  new_uri)
2892             d.addCallback(lambda res:
2893-                          self.failUnlessChildURIIs(self.public_root,
2894-                                                    u"foo",
2895-                                                    self._foo_uri))
2896+                          self.failUnlessRWChildURIIs(self.public_root,
2897+                                                      u"foo",
2898+                                                      self._foo_uri))
2899             return d
2900         d.addCallback(_made_dir)
2901         return d
2902 
2903     def test_PUT_DIRURL_bad_t(self):
2904-        d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
2905+        d = self.shouldFail2(error.Error, "PUT_DIRURL_bad_t",
2906                                  "400 Bad Request", "PUT to a directory",
2907                                  self.PUT, self.public_url + "/foo?t=BOGUS", "")
2908         d.addCallback(lambda res:
2909-                      self.failUnlessChildURIIs(self.public_root,
2910-                                                u"foo",
2911-                                                self._foo_uri))
2912+                      self.failUnlessRWChildURIIs(self.public_root,
2913+                                                  u"foo",
2914+                                                  self._foo_uri))
2915         return d
2916 
2917     def test_PUT_NEWFILEURL_uri(self):
2918         contents, n, new_uri = self.makefile(8)
2919-        d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
2920+        d = self.shouldSucceed("PUT_NEWFILEURL_uri", http.OK, self.PUT,
2921+                               self.public_url + "/foo/new.txt?t=uri", new_uri)
2922         d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2923         d.addCallback(lambda res:
2924                       self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
2925@@ -2564,13 +2785,14 @@
2926 
2927     def test_PUT_NEWFILE_URI(self):
2928         file_contents = "New file contents here\n"
2929-        d = self.PUT("/uri", file_contents)
2930+        d = self.shouldSucceed("PUT_NEWFILE_URI", http.OK, self.PUT,
2931+                               "/uri", file_contents)
2932         def _check(uri):
2933             assert isinstance(uri, str), uri
2934             self.failUnless(uri in FakeCHKFileNode.all_contents)
2935             self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
2936                                  file_contents)
2937-            return self.GET("/uri/%s" % uri)
2938+            return self.shouldSucceedGET("/uri/%s" % uri)
2939         d.addCallback(_check)
2940         def _check2(res):
2941             self.failUnlessEqual(res, file_contents)
2942@@ -2579,13 +2801,14 @@
2943 
2944     def test_PUT_NEWFILE_URI_not_mutable(self):
2945         file_contents = "New file contents here\n"
2946-        d = self.PUT("/uri?mutable=false", file_contents)
2947+        d = self.shouldSucceed("PUT_NEWFILE_URI_not_mutable", http.OK, self.PUT,
2948+                               "/uri?mutable=false", file_contents)
2949         def _check(uri):
2950             assert isinstance(uri, str), uri
2951             self.failUnless(uri in FakeCHKFileNode.all_contents)
2952             self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
2953                                  file_contents)
2954-            return self.GET("/uri/%s" % uri)
2955+            return self.shouldSucceedGET("/uri/%s" % uri)
2956         d.addCallback(_check)
2957         def _check2(res):
2958             self.failUnlessEqual(res, file_contents)
2959@@ -2602,7 +2825,8 @@
2960 
2961     def test_PUT_NEWFILE_URI_mutable(self):
2962         file_contents = "New file contents here\n"
2963-        d = self.PUT("/uri?mutable=true", file_contents)
2964+        d = self.shouldSucceed("PUT_NEWFILE_URI_mutable", http.OK, self.PUT,
2965+                               "/uri?mutable=true", file_contents)
2966         def _check1(filecap):
2967             filecap = filecap.strip()
2968             self.failUnless(filecap.startswith("URI:SSK:"), filecap)
2969@@ -2614,7 +2838,7 @@
2970         d.addCallback(_check1)
2971         def _check2(data):
2972             self.failUnlessEqual(data, file_contents)
2973-            return self.GET("/uri/%s" % urllib.quote(self.filecap))
2974+            return self.shouldSucceedGET("/uri/%s" % urllib.quote(self.filecap))
2975         d.addCallback(_check2)
2976         def _check3(res):
2977             self.failUnlessEqual(res, file_contents)
2978@@ -2622,19 +2846,21 @@
2979         return d
2980 
2981     def test_PUT_mkdir(self):
2982-        d = self.PUT("/uri?t=mkdir", "")
2983+        d = self.shouldSucceed("PUT_mkdir", http.OK, self.PUT,
2984+                               "/uri?t=mkdir", "")
2985         def _check(uri):
2986             n = self.s.create_node_from_uri(uri.strip())
2987             d2 = self.failUnlessNodeKeysAre(n, [])
2988             d2.addCallback(lambda res:
2989-                           self.GET("/uri/%s?t=json" % uri))
2990+                           self.shouldSucceedGET("/uri/%s?t=json" % uri))
2991             return d2
2992         d.addCallback(_check)
2993         d.addCallback(self.failUnlessIsEmptyJSON)
2994         return d
2995 
2996     def test_POST_check(self):
2997-        d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
2998+        d = self.shouldSucceed("POST_check", http.OK, self.POST,
2999+                               self.public_url + "/foo", t="check", name="bar.txt")
3000         def _done(res):
3001             # this returns a string form of the results, which are probably
3002             # None since we're using fake filenodes.
3003@@ -2647,7 +2873,7 @@
3004 
3005     def test_bad_method(self):
3006         url = self.webish_url + self.public_url + "/foo/bar.txt"
3007-        d = self.shouldHTTPError("test_bad_method",
3008+        d = self.shouldHTTPError("bad_method",
3009                                  501, "Not Implemented",
3010                                  "I don't know how to treat a BOGUS request.",
3011                                  client.getPage, url, method="BOGUS")
3012@@ -2655,28 +2881,30 @@
3013 
3014     def test_short_url(self):
3015         url = self.webish_url + "/uri"
3016-        d = self.shouldHTTPError("test_short_url", 501, "Not Implemented",
3017+        d = self.shouldHTTPError("short_url", 501, "Not Implemented",
3018                                  "I don't know how to treat a DELETE request.",
3019                                  client.getPage, url, method="DELETE")
3020         return d
3021 
3022     def test_ophandle_bad(self):
3023         url = self.webish_url + "/operations/bogus?t=status"
3024-        d = self.shouldHTTPError("test_ophandle_bad", 404, "404 Not Found",
3025+        d = self.shouldHTTPError("ophandle_bad", 404, "404 Not Found",
3026                                  "unknown/expired handle 'bogus'",
3027                                  client.getPage, url)
3028         return d
3029 
3030     def test_ophandle_cancel(self):
3031-        d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
3032-                      followRedirect=True)
3033+        d = self.shouldSucceed("ophandle_cancel-1", http.OK, self.POST,
3034+                               self.public_url + "/foo/?t=start-manifest&ophandle=128",
3035+                               followRedirect=True)
3036         d.addCallback(lambda ignored:
3037-                      self.GET("/operations/128?t=status&output=JSON"))
3038+                      self.shouldSucceedGET("/operations/128?t=status&output=JSON"))
3039         def _check1(res):
3040             data = simplejson.loads(res)
3041             self.failUnless("finished" in data, res)
3042             monitor = self.ws.root.child_operations.handles["128"][0]
3043-            d = self.POST("/operations/128?t=cancel&output=JSON")
3044+            d = self.shouldSucceed("ophandle_cancel-2", http.OK, self.POST,
3045+                                   "/operations/128?t=cancel&output=JSON")
3046             def _check2(res):
3047                 data = simplejson.loads(res)
3048                 self.failUnless("finished" in data, res)
3049@@ -2686,7 +2914,7 @@
3050             return d
3051         d.addCallback(_check1)
3052         d.addCallback(lambda ignored:
3053-                      self.shouldHTTPError("test_ophandle_cancel",
3054+                      self.shouldHTTPError("ophandle_cancel",
3055                                            404, "404 Not Found",
3056                                            "unknown/expired handle '128'",
3057                                            self.GET,
3058@@ -2697,7 +2925,7 @@
3059         d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
3060                       followRedirect=True)
3061         d.addCallback(lambda ignored:
3062-                      self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
3063+                      self.shouldSucceedGET("/operations/129?t=status&output=JSON&retain-for=0"))
3064         def _check1(res):
3065             data = simplejson.loads(res)
3066             self.failUnless("finished" in data, res)
3067@@ -2705,7 +2933,7 @@
3068         # the retain-for=0 will cause the handle to be expired very soon
3069         d.addCallback(self.stall, 2.0)
3070         d.addCallback(lambda ignored:
3071-                      self.shouldHTTPError("test_ophandle_retainfor",
3072+                      self.shouldHTTPError("ophandle_retainfor",
3073                                            404, "404 Not Found",
3074                                            "unknown/expired handle '129'",
3075                                            self.GET,
3076@@ -2713,14 +2941,15 @@
3077         return d
3078 
3079     def test_ophandle_release_after_complete(self):
3080-        d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
3081-                      followRedirect=True)
3082+        d = self.shouldSucceed("ophandle_release_after_complete", http.OK, self.POST,
3083+                               self.public_url + "/foo/?t=start-manifest&ophandle=130",
3084+                               followRedirect=True)
3085         d.addCallback(self.wait_for_operation, "130")
3086         d.addCallback(lambda ignored:
3087-                      self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
3088+                      self.shouldSucceedGET("/operations/130?t=status&output=JSON&release-after-complete=true"))
3089         # the release-after-complete=true will cause the handle to be expired
3090         d.addCallback(lambda ignored:
3091-                      self.shouldHTTPError("test_ophandle_release_after_complete",
3092+                      self.shouldHTTPError("ophandle_release_after_complete",
3093                                            404, "404 Not Found",
3094                                            "unknown/expired handle '130'",
3095                                            self.GET,
3096@@ -2728,7 +2957,8 @@
3097         return d
3098 
3099     def test_incident(self):
3100-        d = self.POST("/report_incident", details="eek")
3101+        d = self.shouldSucceed("incident", http.OK, self.POST,
3102+                               "/report_incident", details="eek")
3103         def _done(res):
3104             self.failUnless("Thank you for your report!" in res, res)
3105         d.addCallback(_done)
3106@@ -2741,7 +2971,7 @@
3107         f.write("hello")
3108         f.close()
3109 
3110-        d = self.GET("/static/subdir/hello.txt")
3111+        d = self.shouldSucceedGET("/static/subdir/hello.txt")
3112         def _check(res):
3113             self.failUnlessEqual(res, "hello")
3114         d.addCallback(_check)
3115@@ -2754,7 +2984,7 @@
3116         self.failUnlessEqual(common.parse_replace_arg("false"), False)
3117         self.failUnlessEqual(common.parse_replace_arg("only-files"),
3118                              "only-files")
3119-        self.shouldFail(AssertionError, "test_parse_replace_arg", "",
3120+        self.shouldFail(AssertionError, "parse_replace_arg", "",
3121                         common.parse_replace_arg, "only_fles")
3122 
3123     def test_abbreviate_time(self):
3124@@ -3059,71 +3289,225 @@
3125         d.addErrback(self.explain_web_error)
3126         return d
3127 
3128-    def test_unknown(self):
3129+    def test_unknown(self, immutable=False):
3130         self.basedir = "web/Grid/unknown"
3131+        if immutable:
3132+            self.basedir = "web/Grid/unknown-immutable"
3133+
3134         self.set_up_grid()
3135         c0 = self.g.clients[0]
3136         self.uris = {}
3137         self.fileurls = {}
3138 
3139-        future_writecap = "x-tahoe-crazy://I_am_from_the_future."
3140-        future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future."
3141+        future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
3142+        future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future."
3143         # the future cap format may contain slashes, which must be tolerated
3144-        expected_info_url = "uri/%s?t=info" % urllib.quote(future_writecap,
3145+        expected_info_url = "uri/%s?t=info" % urllib.quote(future_write_uri,
3146                                                            safe="")
3147-        future_node = UnknownNode(future_writecap, future_readcap)
3148 
3149-        d = c0.create_dirnode()
3150+        if immutable:
3151+            name = u"future-imm"
3152+            future_node = UnknownNode(None, future_read_uri, deep_immutable=True)
3153+            d = c0.create_immutable_dirnode({name: (future_node, {})})
3154+        else:
3155+            name = u"future"
3156+            future_node = UnknownNode(future_write_uri, future_read_uri)
3157+            d = c0.create_dirnode()
3158+
3159         def _stash_root_and_create_file(n):
3160             self.rootnode = n
3161             self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
3162             self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
3163-            return self.rootnode.set_node(u"future", future_node)
3164+            if not immutable:
3165+                return self.rootnode.set_node(name, future_node)
3166         d.addCallback(_stash_root_and_create_file)
3167+
3168         # make sure directory listing tolerates unknown nodes
3169         d.addCallback(lambda ign: self.GET(self.rooturl))
3170         def _check_html(res):
3171-            self.failUnlessIn("<td>future</td>", res)
3172-            # find the More Info link for "future", should be relative
3173+            self.failUnlessIn("<td>%s</td>" % (str(name),), res)
3174+            # find the More Info link for name, should be relative
3175             mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
3176             info_url = mo.group(1)
3177-            self.failUnlessEqual(info_url, "future?t=info")
3178+            self.failUnlessEqual(info_url, "%s?t=info" % (str(name),))
3179 
3180         d.addCallback(_check_html)
3181         d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
3182-        def _check_json(res, expect_writecap):
3183+        def _check_json(res, expect_rw_uri):
3184             data = simplejson.loads(res)
3185             self.failUnlessEqual(data[0], "dirnode")
3186-            f = data[1]["children"]["future"]
3187+            f = data[1]["children"][name]
3188             self.failUnlessEqual(f[0], "unknown")
3189-            if expect_writecap:
3190-                self.failUnlessEqual(f[1]["rw_uri"], future_writecap)
3191+            if expect_rw_uri:
3192+                self.failUnlessEqual(f[1]["rw_uri"], future_write_uri)
3193             else:
3194                 self.failIfIn("rw_uri", f[1])
3195-            self.failUnlessEqual(f[1]["ro_uri"], future_readcap)
3196+            self.failUnlessEqual(f[1]["ro_uri"],
3197+                                 ("imm." if immutable else "ro.") + future_read_uri)
3198             self.failUnless("metadata" in f[1])
3199-        d.addCallback(_check_json, expect_writecap=True)
3200-        d.addCallback(lambda ign: self.GET(expected_info_url))
3201-        def _check_info(res, expect_readcap):
3202+        d.addCallback(_check_json, expect_rw_uri=not immutable)
3203+
3204+        def _check_info(res, expect_rw_uri, expect_ro_uri):
3205             self.failUnlessIn("Object Type: <span>unknown</span>", res)
3206-            self.failUnlessIn(future_writecap, res)
3207-            if expect_readcap:
3208-                self.failUnlessIn(future_readcap, res)
3209+            if expect_rw_uri:
3210+                self.failUnlessIn(future_write_uri, res)
3211+            if expect_ro_uri:
3212+                self.failUnlessIn(future_read_uri, res)
3213+            else:
3214+                self.failIfIn(future_read_uri, res)
3215             self.failIfIn("Raw data as", res)
3216             self.failIfIn("Directory writecap", res)
3217             self.failIfIn("Checker Operations", res)
3218             self.failIfIn("Mutable File Operations", res)
3219             self.failIfIn("Directory Operations", res)
3220-        d.addCallback(_check_info, expect_readcap=False)
3221-        d.addCallback(lambda ign: self.GET(self.rooturl+"future?t=info"))
3222-        d.addCallback(_check_info, expect_readcap=True)
3223+
3224+        # Known bug: these should have expect_rw_uri=not immutable, but the
3225+        # info pages are currently broken. Related to ticket #922.
3226+
3227+        d.addCallback(lambda ign: self.GET(expected_info_url))
3228+        d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False)
3229+        d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name))))
3230+        d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True)
3231 
3232         # and make sure that a read-only version of the directory can be
3233-        # rendered too. This version will not have future_writecap
3234+        # rendered too. This version will not have future_write_uri, whether
3235+        # or not future_node was immutable.
3236         d.addCallback(lambda ign: self.GET(self.rourl))
3237         d.addCallback(_check_html)
3238         d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
3239-        d.addCallback(_check_json, expect_writecap=False)
3240+        d.addCallback(_check_json, expect_rw_uri=False)
3241+        return d
3242+
3243+    def test_immutable_unknown(self):
3244+        return self.test_unknown(immutable=True)
3245+
3246+    def test_mutant_dirnodes_are_omitted(self):
3247+        self.basedir = "web/Grid/mutant_dirnodes_are_omitted"
3248+
3249+        self.set_up_grid()
3250+        c = self.g.clients[0]
3251+        nm = c.nodemaker
3252+        self.uris = {}
3253+        self.fileurls = {}
3254+
3255+        lonely_uri = "URI:LIT:n5xgk" # LIT for "one"
3256+        mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
3257+        mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
3258+       
3259+        # This method tests mainly dirnode, but we'd have to duplicate code in order to
3260+        # test the dirnode and web layers separately.
3261+       
3262+        # 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap,
3263+        # and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field.
3264+        # When the directory is read, the mutants should be silently disposed of, leaving
3265+        # their lonely sibling.
3266+        # We don't test the case of a retrieving a cap from the encrypted rw_uri field,
3267+        # because immutable directories don't have a writecap and therefore that field
3268+        # isn't (and can't be) decrypted.
3269+        # TODO: The field still exists in the netstring. Technically we should check what
3270+        # happens if something is put there (it should be ignored), but that can wait.
3271+
3272+        lonely_child = nm.create_from_cap(lonely_uri)
3273+        mutant_ro_child = nm.create_from_cap(mut_read_uri)
3274+        mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri)
3275+
3276+        def _by_hook_or_by_crook():
3277+            return True
3278+        for n in [mutant_ro_child, mutant_write_in_ro_child]:
3279+            n.is_allowed_in_immutable_directory = _by_hook_or_by_crook
3280+
3281+        mutant_write_in_ro_child.get_write_uri    = lambda: None
3282+        mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri
3283+
3284+        kids = {u"lonely":      (lonely_child, {}),
3285+                u"ro":          (mutant_ro_child, {}),
3286+                u"write-in-ro": (mutant_write_in_ro_child, {}),
3287+                }
3288+        d = c.create_immutable_dirnode(kids)
3289+       
3290+        def _created(dn):
3291+            self.failUnless(isinstance(dn, dirnode.DirectoryNode))
3292+            self.failIf(dn.is_mutable())
3293+            self.failUnless(dn.is_readonly())
3294+            # This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail.
3295+            self.failIf(hasattr(dn._node, 'get_writekey'))
3296+            rep = str(dn)
3297+            self.failUnless("RO-IMM" in rep)
3298+            cap = dn.get_cap()
3299+            self.failUnlessIn("CHK", cap.to_string())
3300+            self.cap = cap
3301+            self.rootnode = dn
3302+            self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/"
3303+            return download_to_data(dn._node)
3304+        d.addCallback(_created)
3305+
3306+        def _check_data(data):
3307+            # Decode the netstring representation of the directory to check that all children
3308+            # are present. This is a bit of an abstraction violation, but there's not really
3309+            # any other way to do it given that the real DirectoryNode._unpack_contents would
3310+            # strip the mutant children out (which is what we're trying to test, later).
3311+            position = 0
3312+            numkids = 0
3313+            while position < len(data):
3314+                entries, position = split_netstring(data, 1, position)
3315+                entry = entries[0]
3316+                (name, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
3317+                name = name.decode("utf-8")
3318+                self.failUnless(rwcapdata == "")
3319+                ro_uri = ro_uri.strip()
3320+                if name in kids:
3321+                    self.failIfEqual(ro_uri, "")
3322+                    (expected_child, ign) = kids[name]
3323+                    self.failUnlessEqual(ro_uri, expected_child.get_readonly_uri())
3324+                    numkids += 1
3325+
3326+            self.failUnlessEqual(numkids, 3)
3327+            return self.rootnode.list()
3328+        d.addCallback(_check_data)
3329+       
3330+        # Now when we use the real directory listing code, the mutants should be absent.
3331+        def _check_kids(children):
3332+            self.failUnlessEqual(sorted(children.keys()), [u"lonely"])
3333+            lonely_node, lonely_metadata = children[u"lonely"]
3334+
3335+            self.failUnlessEqual(lonely_node.get_write_uri(), None)
3336+            self.failUnlessEqual(lonely_node.get_readonly_uri(), lonely_uri)
3337+        d.addCallback(_check_kids)
3338+
3339+        d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
3340+        d.addCallback(lambda n: n.list())
3341+        d.addCallback(_check_kids)  # again with dirnode recreated from cap
3342+
3343+        # Make sure the lonely child can be listed in HTML...
3344+        d.addCallback(lambda ign: self.GET(self.rooturl))
3345+        def _check_html(res):
3346+            self.failIfIn("URI:SSK", res)
3347+            get_lonely = "".join([r'<td>FILE</td>',
3348+                                  r'\s+<td>',
3349+                                  r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),),
3350+                                  r'</td>',
3351+                                  r'\s+<td>%d</td>' % len("one"),
3352+                                  ])
3353+            self.failUnless(re.search(get_lonely, res), res)
3354+
3355+            # find the More Info link for name, should be relative
3356+            mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
3357+            info_url = mo.group(1)
3358+            self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url)
3359+        d.addCallback(_check_html)
3360+
3361+        # ... and in JSON.
3362+        d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
3363+        def _check_json(res):
3364+            data = simplejson.loads(res)
3365+            self.failUnlessEqual(data[0], "dirnode")
3366+            listed_children = data[1]["children"]
3367+            self.failUnlessEqual(sorted(listed_children.keys()), [u"lonely"])
3368+            ll_type, ll_data = listed_children[u"lonely"]
3369+            self.failUnlessEqual(ll_type, "filenode")
3370+            self.failIf("rw_uri" in ll_data)
3371+            self.failUnlessEqual(ll_data["ro_uri"], lonely_uri)
3372+        d.addCallback(_check_json)
3373         return d
3374 
3375     def test_deep_check(self):
3376@@ -3156,10 +3540,10 @@
3377 
3378         # this tests that deep-check and stream-manifest will ignore
3379         # UnknownNode instances. Hopefully this will also cover deep-stats.
3380-        future_writecap = "x-tahoe-crazy://I_am_from_the_future."
3381-        future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future."
3382-        future_node = UnknownNode(future_writecap, future_readcap)
3383-        d.addCallback(lambda ign: self.rootnode.set_node(u"future",future_node))
3384+        future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
3385+        future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future."
3386+        future_node = UnknownNode(future_write_uri, future_read_uri)
3387+        d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node))
3388 
3389         def _clobber_shares(ignored):
3390             self.delete_shares_numbered(self.uris["sick"], [0,1])