webish: implement replace= for POST commands
authorBrian Warner <warner@allmydata.com>
Wed, 15 Aug 2007 22:21:38 +0000 (15:21 -0700)
committerBrian Warner <warner@allmydata.com>
Wed, 15 Aug 2007 22:21:38 +0000 (15:21 -0700)
src/allmydata/test/test_web.py
src/allmydata/webish.py

index 1804e30892c67d4be4c95ad56346431f672ef175..5eedb7967a62a263cefcdcd3a3ea4cf3e3bbd376 100644 (file)
@@ -707,6 +707,12 @@ class Web(WebMixin, unittest.TestCase):
                   "409 Conflict",
                   "There was already a child by that name, and you asked me "
                   "to not replace it")
+        def _check(res):
+            self.failUnless("sub" in self._foo_node.children)
+            newdir_uri = self._foo_node.children["sub"]
+            newdir_node = self.nodes[newdir_uri]
+            self.failUnlessEqual(newdir_node.children.keys(), ["baz.txt"])
+        d.addCallback(_check)
         return d
 
     def test_PUT_NEWDIRURL_mkdirs(self):
@@ -924,6 +930,41 @@ class Web(WebMixin, unittest.TestCase):
         d.addCallback(_check)
         return d
 
+    def test_POST_upload_replace(self):
+        d = self.POST("/vdrive/global/foo", t="upload",
+                      file=("bar.txt", self.NEWFILE_CONTENTS))
+        def _check(res):
+            self.failUnless("bar.txt" in self._foo_node.children)
+            new_uri = self._foo_node.children["bar.txt"]
+            new_contents = self.files[new_uri]
+            self.failUnlessEqual(new_contents, self.NEWFILE_CONTENTS)
+            self.failUnlessEqual(res.strip(), new_uri)
+        d.addCallback(_check)
+        return d
+
+    def test_POST_upload_no_replace_queryarg(self):
+        d = self.POST("/vdrive/global/foo?replace=false", t="upload",
+                      file=("bar.txt", self.NEWFILE_CONTENTS))
+        d.addBoth(self.shouldFail, error.Error,
+                  "POST_upload_no_replace_queryarg",
+                  "409 Conflict",
+                  "There was already a child by that name, and you asked me "
+                  "to not replace it")
+        d.addCallback(lambda res: self.GET("/vdrive/global/foo/bar.txt"))
+        d.addCallback(self.failUnlessIsBarDotTxt)
+        return d
+
+    def test_POST_upload_no_replace_field(self):
+        d = self.POST("/vdrive/global/foo", t="upload", replace="false",
+                      file=("bar.txt", self.NEWFILE_CONTENTS))
+        d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
+                  "409 Conflict",
+                  "There was already a child by that name, and you asked me "
+                  "to not replace it")
+        d.addCallback(lambda res: self.GET("/vdrive/global/foo/bar.txt"))
+        d.addCallback(self.failUnlessIsBarDotTxt)
+        return d
+
     def test_POST_upload_whendone(self):
         d = self.POST("/vdrive/global/foo", t="upload", when_done="/THERE",
                       file=("new.txt", self.NEWFILE_CONTENTS))
@@ -975,6 +1016,46 @@ class Web(WebMixin, unittest.TestCase):
         d.addCallback(_check)
         return d
 
+    def test_POST_mkdir_replace(self): # return value?
+        d = self.POST("/vdrive/global/foo", t="mkdir", name="sub")
+        def _check(res):
+            self.failUnless("sub" in self._foo_node.children)
+            newdir_uri = self._foo_node.children["sub"]
+            newdir_node = self.nodes[newdir_uri]
+            self.failIf(newdir_node.children)
+        d.addCallback(_check)
+        return d
+
+    def test_POST_mkdir_no_replace_queryarg(self): # return value?
+        d = self.POST("/vdrive/global/foo?replace=false", t="mkdir", name="sub")
+        d.addBoth(self.shouldFail, error.Error,
+                  "POST_mkdir_no_replace_queryarg",
+                  "409 Conflict",
+                  "There was already a child by that name, and you asked me "
+                  "to not replace it")
+        def _check(res):
+            self.failUnless("sub" in self._foo_node.children)
+            newdir_uri = self._foo_node.children["sub"]
+            newdir_node = self.nodes[newdir_uri]
+            self.failUnlessEqual(newdir_node.children.keys(), ["baz.txt"])
+        d.addCallback(_check)
+        return d
+
+    def test_POST_mkdir_no_replace_field(self): # return value?
+        d = self.POST("/vdrive/global/foo", t="mkdir", name="sub",
+                      replace="false")
+        d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
+                  "409 Conflict",
+                  "There was already a child by that name, and you asked me "
+                  "to not replace it")
+        def _check(res):
+            self.failUnless("sub" in self._foo_node.children)
+            newdir_uri = self._foo_node.children["sub"]
+            newdir_node = self.nodes[newdir_uri]
+            self.failUnlessEqual(newdir_node.children.keys(), ["baz.txt"])
+        d.addCallback(_check)
+        return d
+
     def test_POST_mkdir_whendone_field(self):
         d = self.POST("/vdrive/global/foo",
                       t="mkdir", name="newdir", when_done="/THERE")
@@ -1012,6 +1093,47 @@ class Web(WebMixin, unittest.TestCase):
         d.addCallback(_check)
         return d
 
+    def test_POST_put_uri_replace(self):
+        newuri = self.makefile(8)
+        contents = self.files[newuri]
+        d = self.POST("/vdrive/global/foo", t="uri", name="bar.txt", uri=newuri)
+        def _check(res):
+            self.failUnless("bar.txt" in self._foo_node.children)
+            new_uri = self._foo_node.children["bar.txt"]
+            new_contents = self.files[new_uri]
+            self.failUnlessEqual(new_contents, contents)
+            self.failUnlessEqual(res.strip(), new_uri)
+        d.addCallback(_check)
+        return d
+
+    def test_POST_put_uri_no_replace_queryarg(self):
+        newuri = self.makefile(8)
+        contents = self.files[newuri]
+        d = self.POST("/vdrive/global/foo?replace=false", t="uri",
+                      name="bar.txt", uri=newuri)
+        d.addBoth(self.shouldFail, error.Error,
+                  "POST_put_uri_no_replace_queryarg",
+                  "409 Conflict",
+                  "There was already a child by that name, and you asked me "
+                  "to not replace it")
+        d.addCallback(lambda res: self.GET("/vdrive/global/foo/bar.txt"))
+        d.addCallback(self.failUnlessIsBarDotTxt)
+        return d
+
+    def test_POST_put_uri_no_replace_field(self):
+        newuri = self.makefile(8)
+        contents = self.files[newuri]
+        d = self.POST("/vdrive/global/foo", t="uri", replace="false",
+                      name="bar.txt", uri=newuri)
+        d.addBoth(self.shouldFail, error.Error,
+                  "POST_put_uri_no_replace_field",
+                  "409 Conflict",
+                  "There was already a child by that name, and you asked me "
+                  "to not replace it")
+        d.addCallback(lambda res: self.GET("/vdrive/global/foo/bar.txt"))
+        d.addCallback(self.failUnlessIsBarDotTxt)
+        return d
+
     def test_POST_delete(self):
         d = self.POST("/vdrive/global/foo", t="delete", name="bar.txt")
         def _check(res):
@@ -1032,6 +1154,51 @@ class Web(WebMixin, unittest.TestCase):
         d.addCallback(self.failUnlessIsBarJSON)
         return d
 
+    def test_POST_rename_file_replace(self):
+        # rename a file and replace a directory with it
+        d = self.POST("/vdrive/global/foo", t="rename",
+                      from_name="bar.txt", to_name='empty')
+        def _check(res):
+            self.failIf("bar.txt" in self._foo_node.children)
+            self.failUnless("empty" in self._foo_node.children)
+        d.addCallback(_check)
+        d.addCallback(lambda res: self.GET("/vdrive/global/foo/empty"))
+        d.addCallback(self.failUnlessIsBarDotTxt)
+        d.addCallback(lambda res: self.GET("/vdrive/global/foo/empty?t=json"))
+        d.addCallback(self.failUnlessIsBarJSON)
+        return d
+
+    def test_POST_rename_file_no_replace_queryarg(self):
+        # rename a file and replace a directory with it
+        d = self.POST("/vdrive/global/foo?replace=false", t="rename",
+                      from_name="bar.txt", to_name='empty')
+        d.addBoth(self.shouldFail, error.Error,
+                  "POST_rename_file_no_replace_queryarg",
+                  "409 Conflict",
+                  "There was already a child by that name, and you asked me "
+                  "to not replace it")
+        d.addCallback(lambda res: self.GET("/vdrive/global/foo/empty?t=json"))
+        d.addCallback(self.failUnlessIsEmptyJSON)
+        return d
+
+    def test_POST_rename_file_no_replace_field(self):
+        # rename a file and replace a directory with it
+        d = self.POST("/vdrive/global/foo", t="rename", replace="false",
+                      from_name="bar.txt", to_name='empty')
+        d.addBoth(self.shouldFail, error.Error,
+                  "POST_rename_file_no_replace_field",
+                  "409 Conflict",
+                  "There was already a child by that name, and you asked me "
+                  "to not replace it")
+        d.addCallback(lambda res: self.GET("/vdrive/global/foo/empty?t=json"))
+        d.addCallback(self.failUnlessIsEmptyJSON)
+        return d
+
+    def failUnlessIsEmptyJSON(self, res):
+        data = self.worlds_cheapest_json_decoder(res)
+        self.failUnlessEqual(data[0], "dirnode")
+        self.failUnlessEqual(len(data[1]["children"]), 0)
+
     def test_POST_rename_file_slash_fail(self):
         d = self.POST("/vdrive/global/foo", t="rename",
                       from_name="bar.txt", to_name='kirk/spock.txt')
index 9d0538f1ecf3de06ae89a66c797478f305c4a950..e04f4d3fd5ddbedf4e6ce94081bbbea27db3a774 100644 (file)
@@ -555,6 +555,19 @@ class POSTHandler(rend.Page):
         self._node = node
         self._replace = replace
 
+    def _check_replacement(self, name):
+        if self._replace:
+            return defer.succeed(None)
+        d = self._node.has_child(name)
+        def _got(present):
+            if present:
+                raise NoReplacementError("There was already a child by that "
+                                         "name, and you asked me to not "
+                                         "replace it.")
+            return None
+        d.addCallback(_got)
+        return d
+
     def renderHTTP(self, ctx):
         req = inevow.IRequest(ctx)
 
@@ -579,10 +592,15 @@ class POSTHandler(rend.Page):
         if "when_done" in req.fields:
             when_done = req.fields["when_done"].value
 
+        if "replace" in req.fields:
+            if req.fields["replace"].value.lower() in ("false", "0"):
+                self._replace = False
+
         if t == "mkdir":
             if not name:
                 raise RuntimeError("mkdir requires a name")
-            d = self._node.create_empty_directory(name)
+            d = self._check_replacement(name)
+            d.addCallback(lambda res: self._node.create_empty_directory(name))
             def _done(res):
                 return "directory created"
             d.addCallback(_done)
@@ -593,7 +611,8 @@ class POSTHandler(rend.Page):
                 newuri = req.args["uri"][0]
             else:
                 newuri = req.fields["uri"].value
-            d = self._node.set_uri(name, newuri)
+            d = self._check_replacement(name)
+            d.addCallback(lambda res: self._node.set_uri(name, newuri))
             def _done(res):
                 return newuri
             d.addCallback(_done)
@@ -616,7 +635,8 @@ class POSTHandler(rend.Page):
                     req.setResponseCode(http.BAD_REQUEST)
                     req.setHeader("content-type", "text/plain")
                     return "%s= may not contain a slash" % (k,)
-            d = self._node.get(from_name)
+            d = self._check_replacement(to_name)
+            d.addCallback(lambda res: self._node.get(from_name))
             def add_dest(child):
                 uri = child.get_uri()
                 # now actually do the rename
@@ -632,7 +652,8 @@ class POSTHandler(rend.Page):
             contents = req.fields["file"]
             name = name or contents.filename
             uploadable = upload.FileHandle(contents.file)
-            d = self._node.add_file(name, uploadable)
+            d = self._check_replacement(name)
+            d.addCallback(lambda res: self._node.add_file(name, uploadable))
             def _done(newnode):
                 return newnode.get_uri()
             d.addCallback(_done)
@@ -641,6 +662,17 @@ class POSTHandler(rend.Page):
             return "BAD t=%s" % t
         if when_done:
             d.addCallback(lambda res: url.URL.fromString(when_done))
+        def _check_replacement(f):
+            # TODO: make this more human-friendly: maybe send them to the
+            # when_done page but with an extra query-arg that will display
+            # the error message in a big box at the top of the page. The
+            # directory page that when_done= usually points to accepts a
+            # result= argument.. use that.
+            f.trap(NoReplacementError)
+            req.setResponseCode(http.CONFLICT)
+            req.setHeader("content-type", "text/plain")
+            return str(f.value)
+        d.addErrback(_check_replacement)
         return d
 
 class DELETEHandler(rend.Page):
@@ -877,9 +909,6 @@ class VDrive(rend.Page):
         # TODO: think about clobbering/revealing config files and node secrets
 
         replace = True
-#        if "replace" in req.fields:
-#            if req.fields["replace"].value.lower() in ("false", "0"):
-#                replace = False
         if "replace" in req.args:
             if req.args["replace"][0].lower() in ("false", "0"):
                 replace = False