X-Git-Url: https://git.rkrishnan.org/?a=blobdiff_plain;f=src%2Fallmydata%2Ftest%2Ftest_web.py;h=18514a3a57b012bbebf4cbe3d35b9a9e9d3571cf;hb=e1285d27b9b3203c1e968720177a46ae0a696983;hp=300c19a56f55bd3f688bcc6a6be9cfb1190ea042;hpb=062ae99372d73a02ff376fc3ef66d16dba6be9d7;p=tahoe-lafs%2Ftahoe-lafs.git diff --git a/src/allmydata/test/test_web.py b/src/allmydata/test/test_web.py index 300c19a5..18514a3a 100644 --- a/src/allmydata/test/test_web.py +++ b/src/allmydata/test/test_web.py @@ -1,4 +1,4 @@ -import os.path, re, urllib, time +import os.path, re, urllib, time, cgi import simplejson from StringIO import StringIO @@ -6,7 +6,7 @@ from twisted.application import service from twisted.trial import unittest from twisted.internet import defer, reactor from twisted.internet.task import Clock -from twisted.web import client, error, http, html +from twisted.web import client, error, http from twisted.python import failure, log from foolscap.api import fireEventually, flushEventualQueue @@ -50,7 +50,7 @@ unknown_rocap = u"ro.lafs://readonly_from_the_future_ro_\u263A".encode('utf-8') unknown_immcap = u"imm.lafs://immutable_from_the_future_imm_\u263A".encode('utf-8') FAVICON_MARKUP = '' - +DIR_HTML_TAG = '' class FakeStatsProvider: def get_stats(self): @@ -172,25 +172,34 @@ class FakeHistory: return [] class FakeDisplayableServer(StubServer): - def __init__(self, serverid, nickname): + def __init__(self, serverid, nickname, connected, + last_connect_time, last_loss_time, last_rx_time): StubServer.__init__(self, serverid) self.announcement = {"my-version": "allmydata-tahoe-fake", "service-name": "storage", "nickname": nickname} + self.connected = connected + self.last_loss_time = last_loss_time + self.last_rx_time = last_rx_time + self.last_connect_time = last_connect_time def is_connected(self): - return True + return self.connected def get_permutation_seed(self): return "" def get_remote_host(self): return "" def get_last_loss_time(self): - return None - def get_announcement_time(self): - return None + return self.last_loss_time + def get_last_received_data_time(self): + return self.last_rx_time + def get_last_connect_time(self): + return self.last_connect_time def get_announcement(self): return self.announcement def get_nickname(self): return self.announcement["nickname"] + def get_available_space(self): + return 123456 class FakeBucketCounter(object): def get_state(self): @@ -240,7 +249,13 @@ class FakeClient(Client): self.storage_broker = StorageFarmBroker(None, permute_peers=True) # fake knowledge of another server self.storage_broker.test_add_server("other_nodeid", - FakeDisplayableServer("other_nodeid", u"other_nickname \u263B")) + FakeDisplayableServer( + serverid="other_nodeid", nickname=u"other_nickname \u263B", connected = True, + last_connect_time = 10, last_loss_time = 20, last_rx_time = 30)) + self.storage_broker.test_add_server("disconnected_nodeid", + FakeDisplayableServer( + serverid="other_nodeid", nickname=u"disconnected_nickname \u263B", connected = False, + last_connect_time = 15, last_loss_time = 25, last_rx_time = 35)) self.introducer_client = None self.history = FakeHistory() self.uploader = FakeUploader() @@ -254,6 +269,11 @@ class FakeClient(Client): self.mutable_file_default = SDMF_VERSION self.addService(FakeStorageServer(self.nodeid, self.nickname)) + def get_long_nodeid(self): + return "v0-nodeid" + def get_long_tubid(self): + return "tubid" + def startService(self): return service.MultiService.startService(self) def stopService(self): @@ -261,14 +281,16 @@ class FakeClient(Client): MUTABLE_SIZELIMIT = FakeMutableFileNode.MUTABLE_SIZELIMIT -class WebMixin(object): +class WebMixin(testutil.TimezoneMixin): def setUp(self): + self.setTimezone('UTC-13:00') self.s = FakeClient() self.s.startService() self.staticdir = self.mktemp() self.clock = Clock() + self.fakeTime = 86460 # 1d 0h 1m 0s self.ws = webish.WebishServer(self.s, "0", staticdir=self.staticdir, - clock=self.clock) + clock=self.clock, now_fn=lambda:self.fakeTime) self.ws.setServiceParent(self.s) self.webish_port = self.ws.getPortnum() self.webish_url = self.ws.getURL() @@ -324,8 +346,8 @@ class WebMixin(object): self._htmlname_raw = self._htmlname_unicode.encode('utf-8') self._htmlname_urlencoded = urllib.quote(self._htmlname_raw, '') self._htmlname_escaped = escapeToXML(self._htmlname_raw) - self._htmlname_escaped_attr = html.escape(self._htmlname_raw) - self._htmlname_escaped_double = escapeToXML(html.escape(self._htmlname_raw)) + self._htmlname_escaped_attr = cgi.escape(self._htmlname_raw, quote=True) + self._htmlname_escaped_double = escapeToXML(cgi.escape(self._htmlname_raw, quote=True)) self.HTMLNAME_CONTENTS, n, self._htmlname_txt_uri = self.makefile(0) foo.set_uri(self._htmlname_unicode, self._htmlname_txt_uri, self._htmlname_txt_uri) @@ -563,8 +585,10 @@ class WebMixin(object): res.trap(expected_failure) if substring: self.failUnlessIn(substring, str(res), - "'%s' not in '%s' for test '%s'" % \ - (substring, str(res), which)) + "'%s' not in '%s' (response is '%s') for test '%s'" % \ + (substring, str(res), + getattr(res.value, "response", ""), + which)) if response_substring: self.failUnlessIn(response_substring, res.value.response, "'%s' not in '%s' for test '%s'" % \ @@ -592,7 +616,6 @@ class WebMixin(object): self.fail("%s was supposed to Error(302), not get '%s'" % (which, res)) - class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixin, unittest.TestCase): def test_create(self): pass @@ -600,12 +623,24 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi def test_welcome(self): d = self.GET("/") def _check(res): - self.failUnlessIn('Welcome to Tahoe-LAFS', res) + self.failUnlessIn('Tahoe-LAFS - Welcome', res) self.failUnlessIn(FAVICON_MARKUP, res) - self.failUnlessIn('href="https://tahoe-lafs.org/"', res) + self.failUnlessIn('Recent and Active Operations', res) + self.failUnlessIn('Operational Statistics', res) + self.failUnless(re.search('',res), res) + self.failUnlessIn('Page rendered at', res) + self.failUnlessIn('Tahoe-LAFS code imported from:', res) res_u = res.decode('utf-8') - self.failUnlessIn(u'My nickname: fake_nickname \u263A', res_u) + self.failUnlessIn(u'fake_nickname \u263A', res_u) self.failUnlessIn(u'
other_nickname \u263B
', res_u) + self.failUnlessIn(u'Connected to 1\n of 2 known storage servers', res_u) + self.failUnless(re.search(u'
\n 1d\u00A00h\u00A00m\u00A050s', res_u), repr(res_u)) + self.failUnless(re.search(u'
\n 1d\u00A00h\u00A00m\u00A035s', res_u), repr(res_u)) + self.failUnless(re.search(u'1d\u00A00h\u00A00m\u00A030s', res_u), repr(res_u)) + self.failUnless(re.search(u'1d\u00A00h\u00A00m\u00A025s', res_u), repr(res_u)) + self.failUnlessIn(u'\u00A9 Tahoe-LAFS Software Foundation', res_u) + self.failUnlessIn('

Available

', res) + self.failUnlessIn('123.5kB', res) self.s.basedir = 'web/test_welcome' fileutil.make_dirs("web/test_welcome") @@ -614,6 +649,54 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi d.addCallback(_check) return d + def test_introducer_status(self): + class MockIntroducerClient(object): + def __init__(self, connected): + self.connected = connected + def connected_to_introducer(self): + return self.connected + + d = defer.succeed(None) + + # introducer not connected, unguessable furl + def _set_introducer_not_connected_unguessable(ign): + self.s.introducer_furl = "pb://someIntroducer/secret" + self.s.introducer_client = MockIntroducerClient(False) + return self.GET("/") + d.addCallback(_set_introducer_not_connected_unguessable) + def _check_introducer_not_connected_unguessable(res): + html = res.replace('\n', ' ') + self.failUnlessIn('
pb://someIntroducer/[censored]
', html) + self.failIfIn('pb://someIntroducer/secret', html) + self.failUnless(re.search('', html), res) + d.addCallback(_check_introducer_not_connected_unguessable) + + # introducer connected, unguessable furl + def _set_introducer_connected_unguessable(ign): + self.s.introducer_furl = "pb://someIntroducer/secret" + self.s.introducer_client = MockIntroducerClient(True) + return self.GET("/") + d.addCallback(_set_introducer_connected_unguessable) + def _check_introducer_connected_unguessable(res): + html = res.replace('\n', ' ') + self.failUnlessIn('
pb://someIntroducer/[censored]
', html) + self.failIfIn('pb://someIntroducer/secret', html) + self.failUnless(re.search('', html), res) + d.addCallback(_check_introducer_connected_unguessable) + + # introducer connected, guessable furl + def _set_introducer_connected_guessable(ign): + self.s.introducer_furl = "pb://someIntroducer/introducer" + self.s.introducer_client = MockIntroducerClient(True) + return self.GET("/") + d.addCallback(_set_introducer_connected_guessable) + def _check_introducer_connected_guessable(res): + html = res.replace('\n', ' ') + self.failUnlessIn('
pb://someIntroducer/introducer
', html) + self.failUnless(re.search('', html), res) + d.addCallback(_check_introducer_connected_guessable) + return d + def test_helper_status(self): d = defer.succeed(None) @@ -622,26 +705,36 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi self.s.uploader.helper_furl = None return self.GET("/") d.addCallback(_set_no_helper) - d.addCallback(lambda res: - self.failUnlessIn('Connected to helper?: not configured', res)) + def _check_no_helper(res): + html = res.replace('\n', ' ') + self.failUnless(re.search('', html), res) + d.addCallback(_check_no_helper) # enable helper, not connected def _set_helper_not_connected(ign): - self.s.uploader.helper_furl = "pb://someHelper" + self.s.uploader.helper_furl = "pb://someHelper/secret" self.s.uploader.helper_connected = False return self.GET("/") d.addCallback(_set_helper_not_connected) - d.addCallback(lambda res: - self.failUnlessIn('Connected to helper?: no', res)) + def _check_helper_not_connected(res): + html = res.replace('\n', ' ') + self.failUnlessIn('
pb://someHelper/[censored]
', html) + self.failIfIn('pb://someHelper/secret', html) + self.failUnless(re.search('', html), res) + d.addCallback(_check_helper_not_connected) # enable helper, connected def _set_helper_connected(ign): - self.s.uploader.helper_furl = "pb://someHelper" + self.s.uploader.helper_furl = "pb://someHelper/secret" self.s.uploader.helper_connected = True return self.GET("/") d.addCallback(_set_helper_connected) - d.addCallback(lambda res: - self.failUnlessIn('Connected to helper?: yes', res)) + def _check_helper_connected(res): + html = res.replace('\n', ' ') + self.failUnlessIn('
pb://someHelper/[censored]
', html) + self.failIfIn('pb://someHelper/secret', html) + self.failUnless(re.search('', html), res) + d.addCallback(_check_helper_connected) return d def test_storage(self): @@ -663,7 +756,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi ret_num = h.list_all_retrieve_statuses()[0].get_counter() d = self.GET("/status", followRedirect=True) def _check(res): - self.failUnlessIn('Upload and Download Status', res) + self.failUnlessIn('Recent and Active Operations', res) self.failUnlessIn('"down-%d"' % dl_num, res) self.failUnlessIn('"up-%d"' % ul_num, res) self.failUnlessIn('"mapupdate-%d"' % mu_num, res) @@ -1435,24 +1528,24 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi def _check_upload_and_mkdir_forms(self, html): # We should have a form to create a file, with radio buttons that allow # the user to toggle whether it is a CHK/LIT (default), SDMF, or MDMF file. - self.failUnlessIn('name="t" value="upload"', html) - self.failUnlessIn('input checked="checked" type="radio" id="upload-chk" value="chk" name="format"', html) - self.failUnlessIn('input type="radio" id="upload-sdmf" value="sdmf" name="format"', html) - self.failUnlessIn('input type="radio" id="upload-mdmf" value="mdmf" name="format"', html) + self.failUnless(re.search('', html), html) + self.failUnless(re.search('', html), html) + self.failUnless(re.search('
Return to Welcome page', html) + self.failUnlessIn('
  • Return to Welcome page
  • ', html) self._check_upload_and_mkdir_forms(html) self.failUnlessIn("quux", html) d.addCallback(_check) @@ -1499,7 +1592,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi r'\s+%d' % len(self.BAR_CONTENTS), ]) self.failUnless(re.search(get_bar, res), res) - for label in ['unlink', 'rename/move']: + for label in ['unlink', 'rename/relink']: for line in res.split("\n"): # find the line that contains the relevant button for bar.txt if ("form action" in line and @@ -1546,7 +1639,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty/")) def _check4(res): self.failUnlessIn("directory is empty", res) - MKDIR_BUTTON_RE=re.compile('.*Create a new directory in this directory.*', re.I) + MKDIR_BUTTON_RE=re.compile('.*Create a new directory in this directory.*', re.I) self.failUnless(MKDIR_BUTTON_RE.search(res), res) d.addCallback(_check4) @@ -3184,12 +3277,13 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi d = self.GET("/") def _after_get_welcome_page(res): MKDIR_BUTTON_RE = re.compile( - '
    ' - '' - '', - re.I) - mo = MKDIR_BUTTON_RE.search(res) + '.*' + '[ ]*' + '[ ]*' + '') + html = res.replace('\n', ' ') + mo = MKDIR_BUTTON_RE.search(html) + self.failUnless(mo, html) formaction = mo.group(1) formt = mo.group(2) formaname = mo.group(3) @@ -3439,16 +3533,50 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi d.addCallback(self.failUnlessIsEmptyJSON) return d + def test_POST_rename_file_no_replace_same_link(self): + d = self.POST(self.public_url + "/foo", t="rename", + replace="false", from_name="bar.txt", to_name="bar.txt") + d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"bar.txt")) + d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt")) + d.addCallback(self.failUnlessIsBarDotTxt) + d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json")) + d.addCallback(self.failUnlessIsBarJSON) + return d + + def test_POST_rename_file_replace_only_files(self): + d = self.POST(self.public_url + "/foo", t="rename", + replace="only-files", from_name="bar.txt", + to_name="baz.txt") + d.addCallback(lambda res: self.failIfNodeHasChild(self._foo_node, u"bar.txt")) + d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt")) + d.addCallback(self.failUnlessIsBarDotTxt) + d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt?t=json")) + d.addCallback(self.failUnlessIsBarJSON) + return d + + def test_POST_rename_file_replace_only_files_conflict(self): + d = self.shouldFail2(error.Error, "POST_relink_file_replace_only_files_conflict", + "409 Conflict", + "There was already a child by that name, and you asked me to not replace it.", + self.POST, self.public_url + "/foo", t="relink", + replace="only-files", from_name="bar.txt", + to_name="empty") + d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt")) + d.addCallback(self.failUnlessIsBarDotTxt) + d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json")) + d.addCallback(self.failUnlessIsBarJSON) + return d + def failUnlessIsEmptyJSON(self, res): data = simplejson.loads(res) self.failUnlessEqual(data[0], "dirnode", data) self.failUnlessReallyEqual(len(data[1]["children"]), 0) - def test_POST_rename_file_slash_fail(self): + def test_POST_rename_file_to_slash_fail(self): d = self.POST(self.public_url + "/foo", t="rename", from_name="bar.txt", to_name='kirk/spock.txt') d.addBoth(self.shouldFail, error.Error, - "test_POST_rename_file_slash_fail", + "test_POST_rename_file_to_slash_fail", "400 Bad Request", "to_name= may not contain a slash", ) @@ -3456,6 +3584,18 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi self.failUnlessNodeHasChild(self._foo_node, u"bar.txt")) return d + def test_POST_rename_file_from_slash_fail(self): + d = self.POST(self.public_url + "/foo", t="rename", + from_name="sub/bar.txt", to_name='spock.txt') + d.addBoth(self.shouldFail, error.Error, + "test_POST_rename_from_file_slash_fail", + "400 Bad Request", + "from_name= may not contain a slash", + ) + d.addCallback(lambda res: + self.failUnlessNodeHasChild(self._foo_node, u"bar.txt")) + return d + def test_POST_rename_dir(self): d = self.POST(self.public_url, t="rename", from_name="foo", to_name='plunk') @@ -3467,9 +3607,10 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi d.addCallback(self.failUnlessIsFooJSON) return d - def test_POST_move_file(self): - d = self.POST(self.public_url + "/foo", t="move", - from_name="bar.txt", to_dir="sub") + def test_POST_relink_file(self): + d = self.POST(self.public_url + "/foo", t="relink", + from_name="bar.txt", + to_dir=self.public_root.get_uri() + "/foo/sub") d.addCallback(lambda res: self.failIfNodeHasChild(self._foo_node, u"bar.txt")) d.addCallback(lambda res: @@ -3480,9 +3621,10 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi d.addCallback(self.failUnlessIsBarJSON) return d - def test_POST_move_file_new_name(self): - d = self.POST(self.public_url + "/foo", t="move", - from_name="bar.txt", to_name="wibble.txt", to_dir="sub") + def test_POST_relink_file_new_name(self): + d = self.POST(self.public_url + "/foo", t="relink", + from_name="bar.txt", + to_name="wibble.txt", to_dir=self.public_root.get_uri() + "/foo/sub") d.addCallback(lambda res: self.failIfNodeHasChild(self._foo_node, u"bar.txt")) d.addCallback(lambda res: @@ -3495,9 +3637,10 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi d.addCallback(self.failUnlessIsBarJSON) return d - def test_POST_move_file_replace(self): - d = self.POST(self.public_url + "/foo", t="move", - from_name="bar.txt", to_name="baz.txt", to_dir="sub") + def test_POST_relink_file_replace(self): + d = self.POST(self.public_url + "/foo", t="relink", + from_name="bar.txt", + to_name="baz.txt", to_dir=self.public_root.get_uri() + "/foo/sub") d.addCallback(lambda res: self.failIfNodeHasChild(self._foo_node, u"bar.txt")) d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt")) @@ -3506,13 +3649,13 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi d.addCallback(self.failUnlessIsBarJSON) return d - def test_POST_move_file_no_replace(self): - d = self.shouldFail2(error.Error, "POST_move_file_no_replace", + def test_POST_relink_file_no_replace(self): + d = self.shouldFail2(error.Error, "POST_relink_file_no_replace", "409 Conflict", "There was already a child by that name, and you asked me to not replace it", - self.POST, self.public_url + "/foo", t="move", + self.POST, self.public_url + "/foo", t="relink", replace="false", from_name="bar.txt", - to_name="baz.txt", to_dir="sub") + to_name="baz.txt", to_dir=self.public_root.get_uri() + "/foo/sub") d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt")) d.addCallback(self.failUnlessIsBarDotTxt) d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json")) @@ -3521,13 +3664,49 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi d.addCallback(self.failUnlessIsSubBazDotTxt) return d - def test_POST_move_file_slash_fail(self): + def test_POST_relink_file_no_replace_explicitly_same_link(self): + d = self.POST(self.public_url + "/foo", t="relink", + replace="false", from_name="bar.txt", + to_name="bar.txt", to_dir=self.public_root.get_uri() + "/foo") + d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"bar.txt")) + d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt")) + d.addCallback(self.failUnlessIsBarDotTxt) + d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json")) + d.addCallback(self.failUnlessIsBarJSON) + return d + + def test_POST_relink_file_replace_only_files(self): + d = self.POST(self.public_url + "/foo", t="relink", + replace="only-files", from_name="bar.txt", + to_name="baz.txt", to_dir=self.public_root.get_uri() + "/foo/sub") + d.addCallback(lambda res: + self.failIfNodeHasChild(self._foo_node, u"bar.txt")) + d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt")) + d.addCallback(self.failUnlessIsBarDotTxt) + d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt?t=json")) + d.addCallback(self.failUnlessIsBarJSON) + return d + + def test_POST_relink_file_replace_only_files_conflict(self): + d = self.shouldFail2(error.Error, "POST_relink_file_replace_only_files_conflict", + "409 Conflict", + "There was already a child by that name, and you asked me to not replace it.", + self.POST, self.public_url + "/foo", t="relink", + replace="only-files", from_name="bar.txt", + to_name="sub", to_dir=self.public_root.get_uri() + "/foo") + d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt")) + d.addCallback(self.failUnlessIsBarDotTxt) + d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json")) + d.addCallback(self.failUnlessIsBarJSON) + return d + + def test_POST_relink_file_to_slash_fail(self): d = self.shouldFail2(error.Error, "test_POST_rename_file_slash_fail", "400 Bad Request", "to_name= may not contain a slash", - self.POST, self.public_url + "/foo", t="move", + self.POST, self.public_url + "/foo", t="relink", from_name="bar.txt", - to_name="slash/fail.txt", to_dir="sub") + to_name="slash/fail.txt", to_dir=self.public_root.get_uri() + "/foo/sub") d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"bar.txt")) d.addCallback(lambda res: @@ -3538,38 +3717,58 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi "400 Bad Request", "from_name= may not contain a slash", self.POST, self.public_url + "/foo", - t="move", + t="relink", from_name="nope/bar.txt", - to_name="fail.txt", to_dir="sub")) + to_name="fail.txt", + to_dir=self.public_root.get_uri() + "/foo/sub")) return d - def test_POST_move_file_no_target(self): - d = self.shouldFail2(error.Error, "POST_move_file_no_target", - "400 Bad Request", - "move requires from_name and to_dir", - self.POST, self.public_url + "/foo", t="move", - from_name="bar.txt", to_name="baz.txt") + def test_POST_relink_file_explicitly_same_link(self): + d = self.POST(self.public_url + "/foo", t="relink", + from_name="bar.txt", + to_name="bar.txt", to_dir=self.public_root.get_uri() + "/foo") + d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"bar.txt")) d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt")) d.addCallback(self.failUnlessIsBarDotTxt) d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json")) d.addCallback(self.failUnlessIsBarJSON) + return d + + def test_POST_relink_file_implicitly_same_link(self): + d = self.POST(self.public_url + "/foo", t="relink", + from_name="bar.txt") + d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"bar.txt")) + d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt")) + d.addCallback(self.failUnlessIsBarDotTxt) + d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json")) + d.addCallback(self.failUnlessIsBarJSON) + return d + + def test_POST_relink_file_same_dir(self): + d = self.POST(self.public_url + "/foo", t="relink", + from_name="bar.txt", + to_name="baz.txt", to_dir=self.public_root.get_uri() + "/foo") + d.addCallback(lambda res: self.failIfNodeHasChild(self._foo_node, u"bar.txt")) + d.addCallback(lambda res: self.failUnlessNodeHasChild(self._sub_node, u"baz.txt")) d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt")) - d.addCallback(self.failUnlessIsBazDotTxt) + d.addCallback(self.failUnlessIsBarDotTxt) + d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt?t=json")) + d.addCallback(self.failUnlessIsBarJSON) return d - def test_POST_move_file_bad_target_type(self): - d = self.shouldFail2(error.Error, "test_POST_move_file_bad_target_type", - "400 Bad Request", "invalid target_type parameter", + def test_POST_relink_file_bad_replace(self): + d = self.shouldFail2(error.Error, "test_POST_relink_file_bad_replace", + "400 Bad Request", "invalid replace= argument: 'boogabooga'", self.POST, - self.public_url + "/foo", t="move", - target_type="*D", from_name="bar.txt", - to_dir="sub") + self.public_url + "/foo", t="relink", + replace="boogabooga", from_name="bar.txt", + to_dir=self.public_root.get_uri() + "/foo/sub") return d - def test_POST_move_file_multi_level(self): + def test_POST_relink_file_multi_level(self): d = self.POST(self.public_url + "/foo/sub/level2?t=mkdir", "") - d.addCallback(lambda res: self.POST(self.public_url + "/foo", t="move", - from_name="bar.txt", to_dir="sub/level2")) + d.addCallback(lambda res: self.POST(self.public_url + "/foo", t="relink", + from_name="bar.txt", to_dir=self.public_root.get_uri() + "/foo/sub/level2")) d.addCallback(lambda res: self.failIfNodeHasChild(self._foo_node, u"bar.txt")) d.addCallback(lambda res: self.failIfNodeHasChild(self._sub_node, u"bar.txt")) d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/level2/bar.txt")) @@ -3578,8 +3777,8 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi d.addCallback(self.failUnlessIsBarJSON) return d - def test_POST_move_file_to_uri(self): - d = self.POST(self.public_url + "/foo", t="move", target_type="uri", + def test_POST_relink_file_to_uri(self): + d = self.POST(self.public_url + "/foo", t="relink", target_type="uri", from_name="bar.txt", to_dir=self._sub_uri) d.addCallback(lambda res: self.failIfNodeHasChild(self._foo_node, u"bar.txt")) @@ -3589,18 +3788,20 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi d.addCallback(self.failUnlessIsBarJSON) return d - def test_POST_move_file_to_nonexist_dir(self): - d = self.shouldFail2(error.Error, "POST_move_file_to_nonexist_dir", + def test_POST_relink_file_to_nonexistent_dir(self): + d = self.shouldFail2(error.Error, "POST_relink_file_to_nonexistent_dir", "404 Not Found", "No such child: nopechucktesta", - self.POST, self.public_url + "/foo", t="move", - from_name="bar.txt", to_dir="nopechucktesta") + self.POST, self.public_url + "/foo", t="relink", + from_name="bar.txt", + to_dir=self.public_root.get_uri() + "/nopechucktesta") return d - def test_POST_move_file_into_file(self): - d = self.shouldFail2(error.Error, "POST_move_file_into_file", + def test_POST_relink_file_into_file(self): + d = self.shouldFail2(error.Error, "POST_relink_file_into_file", "400 Bad Request", "to_dir is not a directory", - self.POST, self.public_url + "/foo", t="move", - from_name="bar.txt", to_dir="baz.txt") + self.POST, self.public_url + "/foo", t="relink", + from_name="bar.txt", + to_dir=self.public_root.get_uri() + "/foo/baz.txt") d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt")) d.addCallback(self.failUnlessIsBazDotTxt) d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt")) @@ -3609,11 +3810,11 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi d.addCallback(self.failUnlessIsBarJSON) return d - def test_POST_move_file_to_bad_uri(self): - d = self.shouldFail2(error.Error, "POST_move_file_to_bad_uri", + def test_POST_relink_file_to_bad_uri(self): + d = self.shouldFail2(error.Error, "POST_relink_file_to_bad_uri", "400 Bad Request", "to_dir is not a directory", - self.POST, self.public_url + "/foo", t="move", - from_name="bar.txt", target_type="uri", + self.POST, self.public_url + "/foo", t="relink", + from_name="bar.txt", to_dir="URI:DIR2:mn5jlyjnrjeuydyswlzyui72i:rmneifcj6k6sycjljjhj3f6majsq2zqffydnnul5hfa4j577arma") d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt")) d.addCallback(self.failUnlessIsBarDotTxt) @@ -3621,11 +3822,13 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi d.addCallback(self.failUnlessIsBarJSON) return d - def test_POST_move_dir(self): - d = self.POST(self.public_url + "/foo", t="move", - from_name="bar.txt", to_dir="empty") + def test_POST_relink_dir(self): + d = self.POST(self.public_url + "/foo", t="relink", + from_name="bar.txt", + to_dir=self.public_root.get_uri() + "/foo/empty") d.addCallback(lambda res: self.POST(self.public_url + "/foo", - t="move", from_name="empty", to_dir="sub")) + t="relink", from_name="empty", + to_dir=self.public_root.get_uri() + "/foo/sub")) d.addCallback(lambda res: self.failIfNodeHasChild(self._foo_node, u"empty")) d.addCallback(lambda res: @@ -3690,8 +3893,8 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt", followRedirect=True) def _check(res): - self.failUnlessIn('name="when_done" value="."', res) - self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res)) + self.failUnless(re.search('', res), res) + self.failUnless(re.search(r'', res), res) self.failUnlessIn(FAVICON_MARKUP, res) d.addCallback(_check) return d @@ -4199,7 +4402,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi d = self.POST("/report_incident", details="eek") def _done(res): self.failIfIn("", res) - self.failUnlessIn("Thank you for your report!", res) + self.failUnlessIn("An incident report has been saved", res) d.addCallback(_done) return d @@ -4231,7 +4434,11 @@ class IntroducerWeb(unittest.TestCase): def test_welcome(self): basedir = "web.IntroducerWeb.test_welcome" os.mkdir(basedir) - fileutil.write(os.path.join(basedir, "tahoe.cfg"), "[node]\nweb.port = tcp:0\n") + cfg = "\n".join(["[node]", + "tub.location = 127.0.0.1:1", + "web.port = tcp:0", + ]) + "\n" + fileutil.write(os.path.join(basedir, "tahoe.cfg"), cfg) self.node = IntroducerNode(basedir) self.ws = self.node.getServiceNamed("webish") @@ -4243,6 +4450,8 @@ class IntroducerWeb(unittest.TestCase): def _check(res): self.failUnlessIn('Welcome to the Tahoe-LAFS Introducer', res) self.failUnlessIn(FAVICON_MARKUP, res) + self.failUnlessIn('Page rendered at', res) + self.failUnlessIn('Tahoe-LAFS code imported from:', res) d.addCallback(_check) return d @@ -4273,8 +4482,7 @@ class Util(ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase): self.failUnlessReallyEqual(common.parse_replace_arg("false"), False) self.failUnlessReallyEqual(common.parse_replace_arg("only-files"), "only-files") - self.shouldFail(AssertionError, "test_parse_replace_arg", "", - common.parse_replace_arg, "only_fles") + self.failUnlessRaises(common.WebError, common.parse_replace_arg, "only_fles") def test_abbreviate_time(self): self.failUnlessReallyEqual(common.abbreviate_time(None), "") @@ -4399,7 +4607,7 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi r = simplejson.loads(res) self.failUnlessEqual(r["summary"], "Healthy") self.failUnless(r["results"]["healthy"]) - self.failIf(r["results"]["needs-rebalancing"]) + self.failIfIn("needs-rebalancing", r["results"]) self.failUnless(r["results"]["recoverable"]) d.addCallback(_got_json_good) @@ -4443,8 +4651,8 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi self.failUnlessEqual(r["summary"], "Not Healthy: 9 shares (enc 3-of-10)") self.failIf(r["results"]["healthy"]) - self.failIf(r["results"]["needs-rebalancing"]) self.failUnless(r["results"]["recoverable"]) + self.failIfIn("needs-rebalancing", r["results"]) d.addCallback(_got_json_sick) d.addCallback(self.CHECK, "dead", "t=check") @@ -4457,8 +4665,8 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi self.failUnlessEqual(r["summary"], "Not Healthy: 1 shares (enc 3-of-10)") self.failIf(r["results"]["healthy"]) - self.failIf(r["results"]["needs-rebalancing"]) self.failIf(r["results"]["recoverable"]) + self.failIfIn("needs-rebalancing", r["results"]) d.addCallback(_got_json_dead) d.addCallback(self.CHECK, "corrupt", "t=check&verify=true") @@ -4471,6 +4679,8 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi self.failUnlessIn("Unhealthy: 9 shares (enc 3-of-10)", r["summary"]) self.failIf(r["results"]["healthy"]) self.failUnless(r["results"]["recoverable"]) + self.failIfIn("needs-rebalancing", r["results"]) + self.failUnlessReallyEqual(r["results"]["count-happiness"], 9) self.failUnlessReallyEqual(r["results"]["count-shares-good"], 9) self.failUnlessReallyEqual(r["results"]["count-corrupt-shares"], 1) d.addCallback(_got_json_corrupt) @@ -4919,12 +5129,14 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi self.failUnlessEqual(u0["type"], "directory") self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri()) u0cr = u0["check-results"] + self.failUnlessReallyEqual(u0cr["results"]["count-happiness"], 10) self.failUnlessReallyEqual(u0cr["results"]["count-shares-good"], 10) ugood = [u for u in units if u["type"] == "file" and u["path"] == [u"good"]][0] self.failUnlessReallyEqual(to_str(ugood["cap"]), self.uris["good"]) ugoodcr = ugood["check-results"] + self.failUnlessReallyEqual(ugoodcr["results"]["count-happiness"], 10) self.failUnlessReallyEqual(ugoodcr["results"]["count-shares-good"], 10) stats = units[-1] @@ -5023,6 +5235,7 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi self.failUnlessEqual(last_unit["path"], ["subdir"]) r = last_unit["check-results"]["results"] self.failUnlessReallyEqual(r["count-recoverable-versions"], 0) + self.failUnlessReallyEqual(r["count-happiness"], 1) self.failUnlessReallyEqual(r["count-shares-good"], 1) self.failUnlessReallyEqual(r["recoverable"], False) d.addCallback(_check_broken_deepcheck) @@ -5100,6 +5313,7 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri()) u0crr = u0["check-and-repair-results"] self.failUnlessReallyEqual(u0crr["repair-attempted"], False) + self.failUnlessReallyEqual(u0crr["pre-repair-results"]["results"]["count-happiness"], 10) self.failUnlessReallyEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10) ugood = [u for u in units @@ -5107,6 +5321,7 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi self.failUnlessEqual(to_str(ugood["cap"]), self.uris["good"]) ugoodcrr = ugood["check-and-repair-results"] self.failUnlessReallyEqual(ugoodcrr["repair-attempted"], False) + self.failUnlessReallyEqual(ugoodcrr["pre-repair-results"]["results"]["count-happiness"], 10) self.failUnlessReallyEqual(ugoodcrr["pre-repair-results"]["results"]["count-shares-good"], 10) usick = [u for u in units @@ -5115,7 +5330,9 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi usickcrr = usick["check-and-repair-results"] self.failUnlessReallyEqual(usickcrr["repair-attempted"], True) self.failUnlessReallyEqual(usickcrr["repair-successful"], True) + self.failUnlessReallyEqual(usickcrr["pre-repair-results"]["results"]["count-happiness"], 9) self.failUnlessReallyEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9) + self.failUnlessReallyEqual(usickcrr["post-repair-results"]["results"]["count-happiness"], 10) self.failUnlessReallyEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10) stats = units[-1] @@ -5311,7 +5528,7 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi self.basedir = "web/Grid/exceptions" self.set_up_grid(num_clients=1, num_servers=2) c0 = self.g.clients[0] - c0.DEFAULT_ENCODING_PARAMETERS['happy'] = 2 + c0.encoding_params['happy'] = 2 self.fileurls = {} DATA = "data" * 100 d = c0.create_dirnode() @@ -5400,7 +5617,7 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi d.addCallback(lambda ignored: self.GET(self.fileurls["dir-0share"])) def _check_0shares_dir_html(body): - self.failUnlessIn("", body) + self.failUnlessIn(DIR_HTML_TAG, body) # we should see the regular page, but without the child table or # the dirops forms body = " ".join(body.strip().split()) @@ -5423,7 +5640,7 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi # and some-shares like we did for immutable files (since there # are different sorts of advice to offer in each case). For now, # they present the same way. - self.failUnlessIn("", body) + self.failUnlessIn(DIR_HTML_TAG, body) body = " ".join(body.strip().split()) self.failUnlessIn('href="?t=info">More info on this directory', body) @@ -5550,7 +5767,7 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi d.addCallback(_stash_dir) d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True)) def _check_dir_html(body): - self.failUnlessIn("", body) + self.failUnlessIn(DIR_HTML_TAG, body) self.failUnlessIn("blacklisted.txt", body) d.addCallback(_check_dir_html) d.addCallback(lambda ign: self.GET(self.url)) @@ -5574,7 +5791,7 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi # We should still be able to list the parent directory, in HTML... d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True)) def _check_dir_html2(body): - self.failUnlessIn("", body) + self.failUnlessIn(DIR_HTML_TAG, body) self.failUnlessIn("blacklisted.txt", body) d.addCallback(_check_dir_html2) @@ -5625,7 +5842,7 @@ class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMi self.child_url = "uri/"+dn.get_readonly_uri()+"/child" d.addCallback(_get_dircap) d.addCallback(lambda ign: self.GET(self.dir_url_base, followRedirect=True)) - d.addCallback(lambda body: self.failUnlessIn("", body)) + d.addCallback(lambda body: self.failUnlessIn(DIR_HTML_TAG, body)) d.addCallback(lambda ign: self.GET(self.dir_url_json1)) d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes d.addCallback(lambda ign: self.GET(self.dir_url_json2))