source: trunk/src/allmydata/test/cli/test_put.py

Last change on this file was 1a807a0, checked in by Jean-Paul Calderone <exarkun@…>, at 2023-01-12T21:32:32Z

mollify the type checker

  • Property mode set to 100644
File size: 22.8 KB
Line 
1"""
2Tests for the ``tahoe put`` CLI tool.
3"""
4from __future__ import annotations
5
6from typing import Callable, Awaitable, TypeVar, Any
7import os.path
8from twisted.trial import unittest
9from twisted.python import usage
10from twisted.python.filepath import FilePath
11
12from cryptography.hazmat.primitives.serialization import load_pem_private_key
13
14from allmydata.crypto.rsa import PrivateKey
15from allmydata.uri import from_string
16from allmydata.util import fileutil
17from allmydata.scripts.common import get_aliases
18from allmydata.scripts import cli
19from ..no_network import GridTestMixin
20from ..common_util import skip_if_cannot_represent_filename
21from allmydata.util.encodingutil import get_io_encoding
22from allmydata.util.fileutil import abspath_expanduser_unicode
23from .common import CLITestMixin
24from allmydata.mutable.common import derive_mutable_keys
25
26T = TypeVar("T")
27
28class Put(GridTestMixin, CLITestMixin, unittest.TestCase):
29
30    def test_unlinked_immutable_stdin(self):
31        # tahoe get `echo DATA | tahoe put`
32        # tahoe get `echo DATA | tahoe put -`
33        self.basedir = "cli/Put/unlinked_immutable_stdin"
34        DATA = b"data\xff" * 100
35        self.set_up_grid(oneshare=True)
36        d = self.do_cli("put", stdin=DATA)
37        def _uploaded(res):
38            (rc, out, err) = res
39            self.failUnlessIn("waiting for file data on stdin..", err)
40            self.failUnlessIn("200 OK", err)
41            self.readcap = out
42            self.failUnless(self.readcap.startswith("URI:CHK:"))
43        d.addCallback(_uploaded)
44        d.addCallback(lambda res: self.do_cli("get", self.readcap,
45                                              return_bytes=True))
46        def _downloaded(res):
47            (rc, out, err) = res
48            self.failUnlessReallyEqual(err, b"")
49            self.failUnlessReallyEqual(out, DATA)
50        d.addCallback(_downloaded)
51        d.addCallback(lambda res: self.do_cli("put", "-", stdin=DATA))
52        d.addCallback(lambda rc_out_err:
53                      self.failUnlessReallyEqual(rc_out_err[1], self.readcap))
54        return d
55
56    def test_unlinked_immutable_from_file(self):
57        # tahoe put file.txt
58        # tahoe put ./file.txt
59        # tahoe put /tmp/file.txt
60        # tahoe put ~/file.txt
61        self.basedir = "cli/Put/unlinked_immutable_from_file"
62        self.set_up_grid(oneshare=True)
63
64        rel_fn = str(os.path.join(self.basedir, "DATAFILE"))
65        abs_fn = abspath_expanduser_unicode(rel_fn)
66        # we make the file small enough to fit in a LIT file, for speed
67        fileutil.write(rel_fn, b"short file has some bytes \xff yes")
68        d = self.do_cli_unicode(u"put", [rel_fn])
69        def _uploaded(args):
70            (rc, out, err) = args
71            readcap = out
72            self.failUnless(readcap.startswith("URI:LIT:"), readcap)
73            self.readcap = readcap
74        d.addCallback(_uploaded)
75        d.addCallback(lambda res: self.do_cli_unicode(u"put", [u"./" + rel_fn]))
76        d.addCallback(lambda rc_stdout_stderr:
77                      self.failUnlessReallyEqual(rc_stdout_stderr[1], self.readcap))
78        d.addCallback(lambda res: self.do_cli_unicode(u"put", [abs_fn]))
79        d.addCallback(lambda rc_stdout_stderr:
80                      self.failUnlessReallyEqual(rc_stdout_stderr[1], self.readcap))
81        # we just have to assume that ~ is handled properly
82        return d
83
84    def test_immutable_from_file(self):
85        # tahoe put file.txt uploaded.txt
86        # tahoe - uploaded.txt
87        # tahoe put file.txt subdir/uploaded.txt
88        # tahoe put file.txt tahoe:uploaded.txt
89        # tahoe put file.txt tahoe:subdir/uploaded.txt
90        # tahoe put file.txt DIRCAP:./uploaded.txt
91        # tahoe put file.txt DIRCAP:./subdir/uploaded.txt
92        self.basedir = "cli/Put/immutable_from_file"
93        self.set_up_grid(oneshare=True)
94
95        rel_fn = os.path.join(self.basedir, "DATAFILE")
96        # we make the file small enough to fit in a LIT file, for speed
97        DATA = b"short file"
98        DATA2 = b"short file two"
99        fileutil.write(rel_fn, DATA)
100
101        d = self.do_cli("create-alias", "tahoe")
102
103        d.addCallback(lambda res:
104                      self.do_cli("put", rel_fn, "uploaded.txt"))
105        def _uploaded(args):
106            (rc, out, err) = args
107            readcap = out.strip()
108            self.failUnless(readcap.startswith("URI:LIT:"), readcap)
109            self.failUnlessIn("201 Created", err)
110            self.readcap = readcap
111        d.addCallback(_uploaded)
112        d.addCallback(lambda res:
113                      self.do_cli("get", "tahoe:uploaded.txt",
114                                  return_bytes=True))
115        d.addCallback(lambda rc_stdout_stderr:
116                      self.failUnlessReallyEqual(rc_stdout_stderr[1], DATA))
117
118        d.addCallback(lambda res:
119                      self.do_cli("put", "-", "uploaded.txt", stdin=DATA2))
120        def _replaced(args):
121            (rc, out, err) = args
122            readcap = out.strip()
123            self.failUnless(readcap.startswith("URI:LIT:"), readcap)
124            self.failUnlessIn("200 OK", err)
125        d.addCallback(_replaced)
126
127        d.addCallback(lambda res:
128                      self.do_cli("put", rel_fn, "subdir/uploaded2.txt"))
129        d.addCallback(lambda res: self.do_cli("get", "subdir/uploaded2.txt",
130                                              return_bytes=True))
131        d.addCallback(lambda rc_stdout_stderr:
132                      self.failUnlessReallyEqual(rc_stdout_stderr[1], DATA))
133
134        d.addCallback(lambda res:
135                      self.do_cli("put", rel_fn, "tahoe:uploaded3.txt"))
136        d.addCallback(lambda res: self.do_cli("get", "tahoe:uploaded3.txt",
137                                              return_bytes=True))
138        d.addCallback(lambda rc_stdout_stderr:
139                      self.failUnlessReallyEqual(rc_stdout_stderr[1], DATA))
140
141        d.addCallback(lambda res:
142                      self.do_cli("put", rel_fn, "tahoe:subdir/uploaded4.txt"))
143        d.addCallback(lambda res:
144                      self.do_cli("get", "tahoe:subdir/uploaded4.txt",
145                                  return_bytes=True))
146        d.addCallback(lambda rc_stdout_stderr:
147                      self.failUnlessReallyEqual(rc_stdout_stderr[1], DATA))
148
149        def _get_dircap(res):
150            self.dircap = str(get_aliases(self.get_clientdir())["tahoe"], "ascii")
151        d.addCallback(_get_dircap)
152
153        d.addCallback(lambda res:
154                      self.do_cli("put", rel_fn,
155                                  self.dircap+":./uploaded5.txt"))
156        d.addCallback(lambda res:
157                      self.do_cli("get", "tahoe:uploaded5.txt",
158                                  return_bytes=True))
159        d.addCallback(lambda rc_stdout_stderr:
160                      self.failUnlessReallyEqual(rc_stdout_stderr[1], DATA))
161
162        d.addCallback(lambda res:
163                      self.do_cli("put", rel_fn,
164                                  self.dircap+":./subdir/uploaded6.txt"))
165        d.addCallback(lambda res:
166                      self.do_cli("get", "tahoe:subdir/uploaded6.txt",
167                                  return_bytes=True))
168        d.addCallback(lambda rc_stdout_stderr:
169                      self.failUnlessReallyEqual(rc_stdout_stderr[1], DATA))
170
171        return d
172
173    def test_mutable_unlinked(self):
174        # FILECAP = `echo DATA | tahoe put --mutable`
175        # tahoe get FILECAP, compare against DATA
176        # echo DATA2 | tahoe put - FILECAP
177        # tahoe get FILECAP, compare against DATA2
178        # tahoe put file.txt FILECAP
179        self.basedir = "cli/Put/mutable_unlinked"
180        self.set_up_grid(oneshare=True)
181
182        DATA = b"data" * 100
183        DATA2 = b"two" * 100
184        rel_fn = os.path.join(self.basedir, "DATAFILE")
185        DATA3 = b"three" * 100
186        fileutil.write(rel_fn, DATA3)
187
188        d = self.do_cli("put", "--mutable", stdin=DATA)
189        def _created(res):
190            (rc, out, err) = res
191            self.failUnlessIn("waiting for file data on stdin..", err)
192            self.failUnlessIn("200 OK", err)
193            self.filecap = out
194            self.failUnless(self.filecap.startswith("URI:SSK:"), self.filecap)
195        d.addCallback(_created)
196        d.addCallback(lambda res: self.do_cli("get", self.filecap, return_bytes=True))
197        d.addCallback(lambda rc_out_err: self.failUnlessReallyEqual(rc_out_err[1], DATA))
198
199        d.addCallback(lambda res: self.do_cli("put", "-", self.filecap, stdin=DATA2))
200        def _replaced(res):
201            (rc, out, err) = res
202            self.failUnlessIn("waiting for file data on stdin..", err)
203            self.failUnlessIn("200 OK", err)
204            self.failUnlessReallyEqual(self.filecap, out)
205        d.addCallback(_replaced)
206        d.addCallback(lambda res: self.do_cli("get", self.filecap, return_bytes=True))
207        d.addCallback(lambda rc_out_err: self.failUnlessReallyEqual(rc_out_err[1], DATA2))
208
209        d.addCallback(lambda res: self.do_cli("put", rel_fn, self.filecap))
210        def _replaced2(res):
211            (rc, out, err) = res
212            self.failUnlessIn("200 OK", err)
213            self.failUnlessReallyEqual(self.filecap, out)
214        d.addCallback(_replaced2)
215        d.addCallback(lambda res: self.do_cli("get", self.filecap, return_bytes=True))
216        d.addCallback(lambda rc_out_err: self.failUnlessReallyEqual(rc_out_err[1], DATA3))
217
218        return d
219
220    async def test_unlinked_mutable_specified_private_key(self) -> None:
221        """
222        A new unlinked mutable can be created using a specified private
223        key.
224        """
225        self.basedir = "cli/Put/unlinked-mutable-with-key"
226        await self._test_mutable_specified_key(
227            lambda do_cli, pempath, datapath: do_cli(
228                "put", "--mutable", "--private-key-path", pempath.path,
229                stdin=datapath.getContent(),
230            ),
231        )
232
233    async def test_linked_mutable_specified_private_key(self) -> None:
234        """
235        A new linked mutable can be created using a specified private key.
236        """
237        self.basedir = "cli/Put/linked-mutable-with-key"
238        await self._test_mutable_specified_key(
239            lambda do_cli, pempath, datapath: do_cli(
240                "put", "--mutable", "--private-key-path", pempath.path, datapath.path,
241            ),
242        )
243
244    async def _test_mutable_specified_key(
245            self,
246            run: Callable[[Any, FilePath, FilePath], Awaitable[tuple[int, bytes, bytes]]],
247    ) -> None:
248        """
249        A helper for testing mutable creation.
250
251        :param run: A function to do the creation.  It is called with
252            ``self.do_cli`` and the path to a private key PEM file and a data
253            file.  It returns whatever ``do_cli`` returns.
254        """
255        self.set_up_grid(oneshare=True)
256
257        pempath = FilePath(__file__).parent().sibling("data").child("openssl-rsa-2048.txt")
258        datapath = FilePath(self.basedir).child("data")
259        datapath.setContent(b"Hello world" * 1024)
260
261        (rc, out, err) = await run(self.do_cli, pempath, datapath)
262        self.assertEqual(rc, 0, (out, err))
263        cap = from_string(out.strip())
264        # The capability is derived from the key we specified.
265        privkey = load_pem_private_key(pempath.getContent(), password=None)
266        assert isinstance(privkey, PrivateKey)
267        pubkey = privkey.public_key()
268        writekey, _, fingerprint = derive_mutable_keys((pubkey, privkey))
269        self.assertEqual(
270            (writekey, fingerprint),
271            (cap.writekey, cap.fingerprint),
272        )
273        # Also the capability we were given actually refers to the data we
274        # uploaded.
275        (rc, out, err) = await self.do_cli("get", out.strip())
276        self.assertEqual(rc, 0, (out, err))
277        self.assertEqual(out, datapath.getContent().decode("ascii"))
278
279    def test_mutable(self):
280        # echo DATA1 | tahoe put --mutable - uploaded.txt
281        # echo DATA2 | tahoe put - uploaded.txt # should modify-in-place
282        # tahoe get uploaded.txt, compare against DATA2
283
284        self.basedir = "cli/Put/mutable"
285        self.set_up_grid(oneshare=True)
286
287        DATA1 = b"data" * 100
288        fn1 = os.path.join(self.basedir, "DATA1")
289        fileutil.write(fn1, DATA1)
290        DATA2 = b"two\xff" * 100
291        fn2 = os.path.join(self.basedir, "DATA2")
292        fileutil.write(fn2, DATA2)
293
294        d = self.do_cli("create-alias", "tahoe")
295        d.addCallback(lambda res:
296                      self.do_cli("put", "--mutable", fn1, "tahoe:uploaded.txt"))
297        def _check(res):
298            (rc, out, err) = res
299            self.failUnlessEqual(rc, 0, str(res))
300            self.failUnlessEqual(err.strip(), "201 Created", str(res))
301            self.uri = out
302        d.addCallback(_check)
303        d.addCallback(lambda res:
304                      self.do_cli("put", fn2, "tahoe:uploaded.txt"))
305        def _check2(res):
306            (rc, out, err) = res
307            self.failUnlessEqual(rc, 0, str(res))
308            self.failUnlessEqual(err.strip(), "200 OK", str(res))
309            self.failUnlessEqual(out, self.uri, str(res))
310        d.addCallback(_check2)
311        d.addCallback(lambda res:
312                      self.do_cli("get", "tahoe:uploaded.txt", return_bytes=True))
313        d.addCallback(lambda rc_out_err: self.failUnlessReallyEqual(rc_out_err[1], DATA2))
314        return d
315
316    def _check_mdmf_json(self, args):
317         (rc, json, err) = args
318         self.failUnlessEqual(rc, 0)
319         self.failUnlessEqual(err, "")
320         self.failUnlessIn('"format": "MDMF"', json)
321         # We also want a valid MDMF cap to be in the json.
322         self.failUnlessIn("URI:MDMF", json)
323         self.failUnlessIn("URI:MDMF-RO", json)
324         self.failUnlessIn("URI:MDMF-Verifier", json)
325
326    def _check_sdmf_json(self, args):
327        (rc, json, err) = args
328        self.failUnlessEqual(rc, 0)
329        self.failUnlessEqual(err, "")
330        self.failUnlessIn('"format": "SDMF"', json)
331        # We also want to see the appropriate SDMF caps.
332        self.failUnlessIn("URI:SSK", json)
333        self.failUnlessIn("URI:SSK-RO", json)
334        self.failUnlessIn("URI:SSK-Verifier", json)
335
336    def _check_chk_json(self, args):
337        (rc, json, err) = args
338        self.failUnlessEqual(rc, 0)
339        self.failUnlessEqual(err, "")
340        self.failUnlessIn('"format": "CHK"', json)
341        # We also want to see the appropriate CHK caps.
342        self.failUnlessIn("URI:CHK", json)
343        self.failUnlessIn("URI:CHK-Verifier", json)
344
345    def test_format(self):
346        self.basedir = "cli/Put/format"
347        self.set_up_grid(oneshare=True)
348        data = "data" * 40000 # 160kB total, two segments
349        fn1 = os.path.join(self.basedir, "data")
350        fileutil.write(fn1, data)
351        d = self.do_cli("create-alias", "tahoe")
352
353        def _put_and_ls(ign, cmdargs, expected, filename=None):
354            if filename:
355                args = ["put"] + cmdargs + [fn1, filename]
356            else:
357                # unlinked
358                args = ["put"] + cmdargs + [fn1]
359            d2 = self.do_cli(*args)
360            def _list(args):
361                (rc, out, err) = args
362                self.failUnlessEqual(rc, 0) # don't allow failure
363                if filename:
364                    return self.do_cli("ls", "--json", filename)
365                else:
366                    cap = out.strip()
367                    return self.do_cli("ls", "--json", cap)
368            d2.addCallback(_list)
369            return d2
370
371        # 'tahoe put' to a directory
372        d.addCallback(_put_and_ls, ["--mutable"], "SDMF", "tahoe:s1.txt")
373        d.addCallback(self._check_sdmf_json) # backwards-compatibility
374        d.addCallback(_put_and_ls, ["--format=SDMF"], "SDMF", "tahoe:s2.txt")
375        d.addCallback(self._check_sdmf_json)
376        d.addCallback(_put_and_ls, ["--format=sdmf"], "SDMF", "tahoe:s3.txt")
377        d.addCallback(self._check_sdmf_json)
378        d.addCallback(_put_and_ls, ["--mutable", "--format=SDMF"], "SDMF", "tahoe:s4.txt")
379        d.addCallback(self._check_sdmf_json)
380
381        d.addCallback(_put_and_ls, ["--format=MDMF"], "MDMF", "tahoe:m1.txt")
382        d.addCallback(self._check_mdmf_json)
383        d.addCallback(_put_and_ls, ["--mutable", "--format=MDMF"], "MDMF", "tahoe:m2.txt")
384        d.addCallback(self._check_mdmf_json)
385
386        d.addCallback(_put_and_ls, ["--format=CHK"], "CHK", "tahoe:c1.txt")
387        d.addCallback(self._check_chk_json)
388        d.addCallback(_put_and_ls, [], "CHK", "tahoe:c1.txt")
389        d.addCallback(self._check_chk_json)
390
391        # 'tahoe put' unlinked
392        d.addCallback(_put_and_ls, ["--mutable"], "SDMF")
393        d.addCallback(self._check_sdmf_json) # backwards-compatibility
394        d.addCallback(_put_and_ls, ["--format=SDMF"], "SDMF")
395        d.addCallback(self._check_sdmf_json)
396        d.addCallback(_put_and_ls, ["--format=sdmf"], "SDMF")
397        d.addCallback(self._check_sdmf_json)
398        d.addCallback(_put_and_ls, ["--mutable", "--format=SDMF"], "SDMF")
399        d.addCallback(self._check_sdmf_json)
400
401        d.addCallback(_put_and_ls, ["--format=MDMF"], "MDMF")
402        d.addCallback(self._check_mdmf_json)
403        d.addCallback(_put_and_ls, ["--mutable", "--format=MDMF"], "MDMF")
404        d.addCallback(self._check_mdmf_json)
405
406        d.addCallback(_put_and_ls, ["--format=CHK"], "CHK")
407        d.addCallback(self._check_chk_json)
408        d.addCallback(_put_and_ls, [], "CHK")
409        d.addCallback(self._check_chk_json)
410
411        return d
412
413    def test_put_to_mdmf_cap(self):
414        self.basedir = "cli/Put/put_to_mdmf_cap"
415        self.set_up_grid(oneshare=True)
416        data = "data" * 100000
417        fn1 = os.path.join(self.basedir, "data")
418        fileutil.write(fn1, data)
419        d = self.do_cli("put", "--format=MDMF", fn1)
420        def _got_cap(args):
421            (rc, out, err) = args
422            self.failUnlessEqual(rc, 0)
423            self.cap = out.strip()
424        d.addCallback(_got_cap)
425        # Now try to write something to the cap using put.
426        data2 = "data2" * 100000
427        fn2 = os.path.join(self.basedir, "data2")
428        fileutil.write(fn2, data2)
429        d.addCallback(lambda ignored:
430            self.do_cli("put", fn2, self.cap))
431        def _got_put(args):
432            (rc, out, err) = args
433            self.failUnlessEqual(rc, 0)
434            self.failUnlessIn(self.cap, out)
435        d.addCallback(_got_put)
436        # Now get the cap. We should see the data we just put there.
437        d.addCallback(lambda ignored:
438            self.do_cli("get", self.cap))
439        def _got_data(args):
440            (rc, out, err) = args
441            self.failUnlessEqual(rc, 0)
442            self.failUnlessEqual(out, data2)
443        d.addCallback(_got_data)
444        # add some extension information to the cap and try to put something
445        # to it.
446        def _make_extended_cap(ignored):
447            self.cap = self.cap + ":Extension-Stuff"
448        d.addCallback(_make_extended_cap)
449        data3 = "data3" * 100000
450        fn3 = os.path.join(self.basedir, "data3")
451        fileutil.write(fn3, data3)
452        d.addCallback(lambda ignored:
453            self.do_cli("put", fn3, self.cap))
454        d.addCallback(lambda ignored:
455            self.do_cli("get", self.cap))
456        def _got_data3(args):
457            (rc, out, err) = args
458            self.failUnlessEqual(rc, 0)
459            self.failUnlessEqual(out, data3)
460        d.addCallback(_got_data3)
461        return d
462
463    def test_put_to_sdmf_cap(self):
464        self.basedir = "cli/Put/put_to_sdmf_cap"
465        self.set_up_grid(oneshare=True)
466        data = "data" * 100000
467        fn1 = os.path.join(self.basedir, "data")
468        fileutil.write(fn1, data)
469        d = self.do_cli("put", "--format=SDMF", fn1)
470        def _got_cap(args):
471            (rc, out, err) = args
472            self.failUnlessEqual(rc, 0)
473            self.cap = out.strip()
474        d.addCallback(_got_cap)
475        # Now try to write something to the cap using put.
476        data2 = "data2" * 100000
477        fn2 = os.path.join(self.basedir, "data2")
478        fileutil.write(fn2, data2)
479        d.addCallback(lambda ignored:
480            self.do_cli("put", fn2, self.cap))
481        def _got_put(args):
482            (rc, out, err) = args
483            self.failUnlessEqual(rc, 0)
484            self.failUnlessIn(self.cap, out)
485        d.addCallback(_got_put)
486        # Now get the cap. We should see the data we just put there.
487        d.addCallback(lambda ignored:
488            self.do_cli("get", self.cap))
489        def _got_data(args):
490            (rc, out, err) = args
491            self.failUnlessEqual(rc, 0)
492            self.failUnlessEqual(out, data2)
493        d.addCallback(_got_data)
494        return d
495
496    def test_mutable_type_invalid_format(self):
497        o = cli.PutOptions()
498        self.failUnlessRaises(usage.UsageError,
499                              o.parseOptions,
500                              ["--format=LDMF"])
501
502    def test_put_with_nonexistent_alias(self):
503        # when invoked with an alias that doesn't exist, 'tahoe put'
504        # should output a useful error message, not a stack trace
505        self.basedir = "cli/Put/put_with_nonexistent_alias"
506        self.set_up_grid(oneshare=True)
507        d = self.do_cli("put", "somefile", "fake:afile")
508        def _check(args):
509            (rc, out, err) = args
510            self.failUnlessReallyEqual(rc, 1)
511            self.failUnlessIn("error:", err)
512            self.assertEqual(len(out), 0, out)
513        d.addCallback(_check)
514        return d
515
516    def test_immutable_from_file_unicode(self):
517        # tahoe put "\u00E0 trier.txt" "\u00E0 trier.txt"
518
519        a_trier_arg = u"\u00E0 trier.txt"
520
521        skip_if_cannot_represent_filename(u"\u00E0 trier.txt")
522
523        self.basedir = "cli/Put/immutable_from_file_unicode"
524        self.set_up_grid(oneshare=True)
525
526        rel_fn = os.path.join(str(self.basedir), u"\u00E0 trier.txt")
527        # we make the file small enough to fit in a LIT file, for speed
528        DATA = b"short file \xff bytes"
529        fileutil.write(rel_fn, DATA)
530
531        d = self.do_cli("create-alias", "tahoe")
532
533        d.addCallback(lambda res:
534                      self.do_cli("put", rel_fn.encode(get_io_encoding()), a_trier_arg))
535        def _uploaded(args):
536            (rc, out, err) = args
537            readcap = out.strip()
538            self.failUnless(readcap.startswith("URI:LIT:"), readcap)
539            self.failUnlessIn("201 Created", err)
540            self.readcap = readcap
541        d.addCallback(_uploaded)
542
543        d.addCallback(lambda res:
544                      self.do_cli("get", "tahoe:" + a_trier_arg,
545                                  return_bytes=True))
546        d.addCallback(lambda rc_out_err:
547                      self.failUnlessReallyEqual(rc_out_err[1], DATA))
548
549        return d
550
551    def test_no_leading_slash(self):
552        self.basedir = "cli/Put/leading_slash"
553        self.set_up_grid(oneshare=True)
554
555        fn1 = os.path.join(self.basedir, "DATA1")
556
557        d = self.do_cli("create-alias", "tahoe")
558        d.addCallback(lambda res:
559                      self.do_cli("put", fn1, "tahoe:/uploaded.txt"))
560        def _check(args):
561            (rc, out, err) = args
562            self.assertEqual(rc, 1)
563            self.failUnlessIn("must not start with a slash", err)
564            self.assertEqual(len(out), 0, out)
565        d.addCallback(_check)
566        return d
Note: See TracBrowser for help on using the repository browser.