| 1 | """ |
|---|
| 2 | Ported to Python 3. |
|---|
| 3 | """ |
|---|
| 4 | |
|---|
| 5 | from io import StringIO |
|---|
| 6 | import os.path |
|---|
| 7 | from twisted.trial import unittest |
|---|
| 8 | from urllib.parse import quote as url_quote |
|---|
| 9 | |
|---|
| 10 | from allmydata.util import fileutil |
|---|
| 11 | from allmydata.scripts.common import get_aliases |
|---|
| 12 | from allmydata.scripts import cli, runner |
|---|
| 13 | from ..no_network import GridTestMixin |
|---|
| 14 | from allmydata.util.encodingutil import quote_output_u |
|---|
| 15 | from .common import CLITestMixin |
|---|
| 16 | |
|---|
| 17 | class CreateAlias(GridTestMixin, CLITestMixin, unittest.TestCase): |
|---|
| 18 | |
|---|
| 19 | def _test_webopen(self, args, expected_url): |
|---|
| 20 | o = runner.Options() |
|---|
| 21 | o.parseOptions(["--node-directory", self.get_clientdir(), "webopen"] |
|---|
| 22 | + list(args)) |
|---|
| 23 | urls = [] |
|---|
| 24 | o.subOptions.stdout = StringIO() |
|---|
| 25 | o.subOptions.stderr = StringIO() |
|---|
| 26 | o.subOptions.stdin = StringIO() |
|---|
| 27 | rc = cli.webopen(o.subOptions, urls.append) |
|---|
| 28 | self.failUnlessReallyEqual(rc, 0) |
|---|
| 29 | self.failUnlessReallyEqual(len(urls), 1) |
|---|
| 30 | self.assertEqual(urls[0], expected_url) |
|---|
| 31 | |
|---|
| 32 | def test_create(self): |
|---|
| 33 | self.basedir = "cli/CreateAlias/create" |
|---|
| 34 | self.set_up_grid(oneshare=True) |
|---|
| 35 | aliasfile = os.path.join(self.get_clientdir(), "private", "aliases") |
|---|
| 36 | |
|---|
| 37 | d = self.do_cli("create-alias", "tahoe") |
|---|
| 38 | def _done(args): |
|---|
| 39 | (rc, stdout, stderr) = args |
|---|
| 40 | self.assertEqual(stderr, "") |
|---|
| 41 | self.assertIn("Alias 'tahoe' created", stdout) |
|---|
| 42 | aliases = get_aliases(self.get_clientdir()) |
|---|
| 43 | self.failUnless("tahoe" in aliases) |
|---|
| 44 | self.failUnless(aliases["tahoe"].startswith(b"URI:DIR2:")) |
|---|
| 45 | d.addCallback(_done) |
|---|
| 46 | d.addCallback(lambda res: self.do_cli("create-alias", "two:")) |
|---|
| 47 | |
|---|
| 48 | def _stash_urls(res): |
|---|
| 49 | aliases = get_aliases(self.get_clientdir()) |
|---|
| 50 | node_url_file = os.path.join(self.get_clientdir(), "node.url") |
|---|
| 51 | nodeurl = fileutil.read(node_url_file, mode="r").strip() |
|---|
| 52 | self.welcome_url = nodeurl |
|---|
| 53 | uribase = nodeurl + "uri/" |
|---|
| 54 | self.tahoe_url = uribase + url_quote(aliases["tahoe"]) |
|---|
| 55 | self.tahoe_subdir_url = self.tahoe_url + "/subdir" |
|---|
| 56 | self.two_url = uribase + url_quote(aliases["two"]) |
|---|
| 57 | self.two_uri = aliases["two"] |
|---|
| 58 | d.addCallback(_stash_urls) |
|---|
| 59 | |
|---|
| 60 | d.addCallback(lambda res: self.do_cli("create-alias", "two")) # dup |
|---|
| 61 | def _check_create_duplicate(args): |
|---|
| 62 | (rc, stdout, stderr) = args |
|---|
| 63 | self.failIfEqual(rc, 0) |
|---|
| 64 | self.failUnless("Alias 'two' already exists!" in stderr) |
|---|
| 65 | aliases = get_aliases(self.get_clientdir()) |
|---|
| 66 | self.failUnlessReallyEqual(aliases["two"], self.two_uri) |
|---|
| 67 | d.addCallback(_check_create_duplicate) |
|---|
| 68 | |
|---|
| 69 | d.addCallback(lambda res: self.do_cli("add-alias", "added", self.two_uri)) |
|---|
| 70 | def _check_add(args): |
|---|
| 71 | (rc, stdout, stderr) = args |
|---|
| 72 | self.failUnlessReallyEqual(rc, 0) |
|---|
| 73 | self.failUnless("Alias 'added' added" in stdout) |
|---|
| 74 | d.addCallback(_check_add) |
|---|
| 75 | |
|---|
| 76 | # check add-alias with a duplicate |
|---|
| 77 | d.addCallback(lambda res: self.do_cli("add-alias", "two", self.two_uri)) |
|---|
| 78 | def _check_add_duplicate(args): |
|---|
| 79 | (rc, stdout, stderr) = args |
|---|
| 80 | self.failIfEqual(rc, 0) |
|---|
| 81 | self.failUnless("Alias 'two' already exists!" in stderr) |
|---|
| 82 | aliases = get_aliases(self.get_clientdir()) |
|---|
| 83 | self.failUnlessReallyEqual(aliases["two"], self.two_uri) |
|---|
| 84 | d.addCallback(_check_add_duplicate) |
|---|
| 85 | |
|---|
| 86 | # check create-alias and add-alias with invalid aliases |
|---|
| 87 | def _check_invalid(args): |
|---|
| 88 | (rc, stdout, stderr) = args |
|---|
| 89 | self.failIfEqual(rc, 0) |
|---|
| 90 | self.failUnlessIn("cannot contain", stderr) |
|---|
| 91 | |
|---|
| 92 | for invalid in ['foo:bar', 'foo bar', 'foobar::']: |
|---|
| 93 | d.addCallback(lambda res, invalid=invalid: self.do_cli("create-alias", invalid)) |
|---|
| 94 | d.addCallback(_check_invalid) |
|---|
| 95 | d.addCallback(lambda res, invalid=invalid: self.do_cli("add-alias", invalid, self.two_uri)) |
|---|
| 96 | d.addCallback(_check_invalid) |
|---|
| 97 | |
|---|
| 98 | def _test_urls(junk): |
|---|
| 99 | self._test_webopen([], self.welcome_url) |
|---|
| 100 | self._test_webopen(["/"], self.tahoe_url) |
|---|
| 101 | self._test_webopen(["tahoe:"], self.tahoe_url) |
|---|
| 102 | self._test_webopen(["tahoe:/"], self.tahoe_url) |
|---|
| 103 | self._test_webopen(["tahoe:subdir"], self.tahoe_subdir_url) |
|---|
| 104 | self._test_webopen(["-i", "tahoe:subdir"], |
|---|
| 105 | self.tahoe_subdir_url+"?t=info") |
|---|
| 106 | self._test_webopen(["tahoe:subdir/"], self.tahoe_subdir_url + '/') |
|---|
| 107 | self._test_webopen(["tahoe:subdir/file"], |
|---|
| 108 | self.tahoe_subdir_url + '/file') |
|---|
| 109 | self._test_webopen(["--info", "tahoe:subdir/file"], |
|---|
| 110 | self.tahoe_subdir_url + '/file?t=info') |
|---|
| 111 | # if "file" is indeed a file, then the url produced by webopen in |
|---|
| 112 | # this case is disallowed by the webui. but by design, webopen |
|---|
| 113 | # passes through the mistake from the user to the resultant |
|---|
| 114 | # webopened url |
|---|
| 115 | self._test_webopen(["tahoe:subdir/file/"], self.tahoe_subdir_url + '/file/') |
|---|
| 116 | self._test_webopen(["two:"], self.two_url) |
|---|
| 117 | d.addCallback(_test_urls) |
|---|
| 118 | |
|---|
| 119 | def _remove_trailing_newline_and_create_alias(ign): |
|---|
| 120 | # ticket #741 is about a manually-edited alias file (which |
|---|
| 121 | # doesn't end in a newline) being corrupted by a subsequent |
|---|
| 122 | # "tahoe create-alias" |
|---|
| 123 | old = fileutil.read(aliasfile) |
|---|
| 124 | fileutil.write(aliasfile, old.rstrip()) |
|---|
| 125 | return self.do_cli("create-alias", "un-corrupted1") |
|---|
| 126 | d.addCallback(_remove_trailing_newline_and_create_alias) |
|---|
| 127 | def _check_not_corrupted1(args): |
|---|
| 128 | (rc, stdout, stderr) = args |
|---|
| 129 | self.failUnless("Alias 'un-corrupted1' created" in stdout, stdout) |
|---|
| 130 | self.failIf(stderr) |
|---|
| 131 | # the old behavior was to simply append the new record, causing a |
|---|
| 132 | # line that looked like "NAME1: CAP1NAME2: CAP2". This won't look |
|---|
| 133 | # like a valid dircap, so get_aliases() will raise an exception. |
|---|
| 134 | aliases = get_aliases(self.get_clientdir()) |
|---|
| 135 | self.failUnless("added" in aliases) |
|---|
| 136 | self.failUnless(aliases["added"].startswith(b"URI:DIR2:")) |
|---|
| 137 | # to be safe, let's confirm that we don't see "NAME2:" in CAP1. |
|---|
| 138 | # No chance of a false-negative, because the hyphen in |
|---|
| 139 | # "un-corrupted1" is not a valid base32 character. |
|---|
| 140 | self.failIfIn(b"un-corrupted1:", aliases["added"]) |
|---|
| 141 | self.failUnless("un-corrupted1" in aliases) |
|---|
| 142 | self.failUnless(aliases["un-corrupted1"].startswith(b"URI:DIR2:")) |
|---|
| 143 | d.addCallback(_check_not_corrupted1) |
|---|
| 144 | |
|---|
| 145 | def _remove_trailing_newline_and_add_alias(ign): |
|---|
| 146 | # same thing, but for "tahoe add-alias" |
|---|
| 147 | old = fileutil.read(aliasfile) |
|---|
| 148 | fileutil.write(aliasfile, old.rstrip()) |
|---|
| 149 | return self.do_cli("add-alias", "un-corrupted2", self.two_uri) |
|---|
| 150 | d.addCallback(_remove_trailing_newline_and_add_alias) |
|---|
| 151 | def _check_not_corrupted(args): |
|---|
| 152 | (rc, stdout, stderr) = args |
|---|
| 153 | self.failUnless("Alias 'un-corrupted2' added" in stdout, stdout) |
|---|
| 154 | self.failIf(stderr) |
|---|
| 155 | aliases = get_aliases(self.get_clientdir()) |
|---|
| 156 | self.failUnless("un-corrupted1" in aliases) |
|---|
| 157 | self.failUnless(aliases["un-corrupted1"].startswith(b"URI:DIR2:")) |
|---|
| 158 | self.failIfIn(b"un-corrupted2:", aliases["un-corrupted1"]) |
|---|
| 159 | self.failUnless("un-corrupted2" in aliases) |
|---|
| 160 | self.failUnless(aliases["un-corrupted2"].startswith(b"URI:DIR2:")) |
|---|
| 161 | d.addCallback(_check_not_corrupted) |
|---|
| 162 | return d |
|---|
| 163 | |
|---|
| 164 | def test_create_unicode(self): |
|---|
| 165 | self.basedir = "cli/CreateAlias/create_unicode" |
|---|
| 166 | self.set_up_grid(oneshare=True) |
|---|
| 167 | |
|---|
| 168 | etudes_arg = u"\u00E9tudes" |
|---|
| 169 | lumiere_arg = u"lumi\u00E8re.txt" |
|---|
| 170 | |
|---|
| 171 | d = self.do_cli("create-alias", etudes_arg) |
|---|
| 172 | def _check_create_unicode(args): |
|---|
| 173 | (rc, out, err) = args |
|---|
| 174 | self.failUnlessReallyEqual(rc, 0) |
|---|
| 175 | self.assertEqual(len(err), 0, err) |
|---|
| 176 | self.failUnlessIn(u"Alias %s created" % (quote_output_u(etudes_arg),), out) |
|---|
| 177 | |
|---|
| 178 | aliases = get_aliases(self.get_clientdir()) |
|---|
| 179 | self.failUnless(aliases[u"\u00E9tudes"].startswith(b"URI:DIR2:")) |
|---|
| 180 | d.addCallback(_check_create_unicode) |
|---|
| 181 | |
|---|
| 182 | d.addCallback(lambda res: self.do_cli("ls", etudes_arg + ":")) |
|---|
| 183 | def _check_ls1(args): |
|---|
| 184 | (rc, out, err) = args |
|---|
| 185 | self.failUnlessReallyEqual(rc, 0) |
|---|
| 186 | self.assertEqual(len(err), 0, err) |
|---|
| 187 | self.assertEqual(len(out), 0, out) |
|---|
| 188 | d.addCallback(_check_ls1) |
|---|
| 189 | |
|---|
| 190 | DATA = b"Blah blah blah \xff blah \x00 blah" |
|---|
| 191 | d.addCallback(lambda res: self.do_cli("put", "-", etudes_arg + ":uploaded.txt", |
|---|
| 192 | stdin=DATA)) |
|---|
| 193 | |
|---|
| 194 | d.addCallback(lambda res: self.do_cli("ls", etudes_arg + ":")) |
|---|
| 195 | def _check_ls2(args): |
|---|
| 196 | (rc, out, err) = args |
|---|
| 197 | self.failUnlessReallyEqual(rc, 0) |
|---|
| 198 | self.assertEqual(len(err), 0, err) |
|---|
| 199 | self.assertEqual(out, "uploaded.txt\n") |
|---|
| 200 | d.addCallback(_check_ls2) |
|---|
| 201 | |
|---|
| 202 | d.addCallback(lambda res: self.do_cli("get", etudes_arg + ":uploaded.txt", |
|---|
| 203 | return_bytes=True)) |
|---|
| 204 | def _check_get(args): |
|---|
| 205 | (rc, out, err) = args |
|---|
| 206 | self.failUnlessReallyEqual(rc, 0) |
|---|
| 207 | self.assertEqual(len(err), 0, err) |
|---|
| 208 | self.failUnlessReallyEqual(out, DATA) |
|---|
| 209 | d.addCallback(_check_get) |
|---|
| 210 | |
|---|
| 211 | # Ensure that an Unicode filename in an Unicode alias works as expected |
|---|
| 212 | d.addCallback(lambda res: self.do_cli("put", "-", etudes_arg + ":" + lumiere_arg, |
|---|
| 213 | stdin=b"Let the sunshine In!")) |
|---|
| 214 | |
|---|
| 215 | d.addCallback(lambda res: self.do_cli( |
|---|
| 216 | "get", |
|---|
| 217 | str(get_aliases(self.get_clientdir())[u"\u00E9tudes"], "ascii") + "/" + lumiere_arg, |
|---|
| 218 | return_bytes=True)) |
|---|
| 219 | def _check_get2(args): |
|---|
| 220 | (rc, out, err) = args |
|---|
| 221 | self.failUnlessReallyEqual(rc, 0) |
|---|
| 222 | self.assertEqual(len(err), 0, err) |
|---|
| 223 | self.failUnlessReallyEqual(out, b"Let the sunshine In!") |
|---|
| 224 | d.addCallback(_check_get2) |
|---|
| 225 | |
|---|
| 226 | return d |
|---|
| 227 | |
|---|
| 228 | # TODO: test list-aliases, including Unicode |
|---|