| 1 | """ |
|---|
| 2 | Ported to Python 3. |
|---|
| 3 | """ |
|---|
| 4 | |
|---|
| 5 | import json |
|---|
| 6 | from os.path import join |
|---|
| 7 | |
|---|
| 8 | from bs4 import BeautifulSoup |
|---|
| 9 | |
|---|
| 10 | from twisted.internet import reactor |
|---|
| 11 | from twisted.internet import defer |
|---|
| 12 | from testtools.twistedsupport import succeeded |
|---|
| 13 | |
|---|
| 14 | from ..common import ( |
|---|
| 15 | SyncTestCase, |
|---|
| 16 | AsyncTestCase, |
|---|
| 17 | ) |
|---|
| 18 | |
|---|
| 19 | from foolscap.api import ( |
|---|
| 20 | fireEventually, |
|---|
| 21 | flushEventualQueue, |
|---|
| 22 | Tub, |
|---|
| 23 | ) |
|---|
| 24 | |
|---|
| 25 | import allmydata |
|---|
| 26 | from allmydata.introducer import ( |
|---|
| 27 | create_introducer, |
|---|
| 28 | ) |
|---|
| 29 | from allmydata.introducer.server import ( |
|---|
| 30 | _IntroducerNode, |
|---|
| 31 | ) |
|---|
| 32 | from allmydata.web.introweb import ( |
|---|
| 33 | IntroducerRoot, |
|---|
| 34 | ) |
|---|
| 35 | |
|---|
| 36 | from allmydata import node |
|---|
| 37 | from .common import ( |
|---|
| 38 | assert_soup_has_favicon, |
|---|
| 39 | assert_soup_has_text, |
|---|
| 40 | assert_soup_has_tag_with_attributes, |
|---|
| 41 | ) |
|---|
| 42 | from ..common import ( |
|---|
| 43 | SameProcessStreamEndpointAssigner, |
|---|
| 44 | ) |
|---|
| 45 | from ..common_util import ( |
|---|
| 46 | FakeCanary, |
|---|
| 47 | ) |
|---|
| 48 | from ..common_web import ( |
|---|
| 49 | do_http, |
|---|
| 50 | render, |
|---|
| 51 | ) |
|---|
| 52 | |
|---|
| 53 | from testtools.matchers import ( |
|---|
| 54 | Equals, |
|---|
| 55 | AfterPreprocessing, |
|---|
| 56 | ) |
|---|
| 57 | |
|---|
| 58 | |
|---|
| 59 | @defer.inlineCallbacks |
|---|
| 60 | def create_introducer_webish(reactor, port_assigner, basedir): |
|---|
| 61 | """ |
|---|
| 62 | Create and start an introducer node and return it and its ``WebishServer`` |
|---|
| 63 | service. |
|---|
| 64 | |
|---|
| 65 | :param reactor: The reactor to use to allow the introducer node to use to |
|---|
| 66 | listen for connections. |
|---|
| 67 | |
|---|
| 68 | :param SameProcessStreamEndpointAssigner port_assigner: The assigner to |
|---|
| 69 | use to assign a listening port for the introducer node. |
|---|
| 70 | |
|---|
| 71 | :param bytes basedir: A non-existant path where the introducer node will |
|---|
| 72 | be created. |
|---|
| 73 | |
|---|
| 74 | :return Deferred[(_IntroducerNode, WebishServer)]: A Deferred that fires |
|---|
| 75 | with the node and its webish service. |
|---|
| 76 | """ |
|---|
| 77 | node.create_node_dir(basedir, "testing") |
|---|
| 78 | main_tub_location, main_tub_endpoint = port_assigner.assign(reactor) |
|---|
| 79 | _, web_port_endpoint = port_assigner.assign(reactor) |
|---|
| 80 | with open(join(basedir, "tahoe.cfg"), "w") as f: |
|---|
| 81 | f.write( |
|---|
| 82 | "[node]\n" |
|---|
| 83 | "tub.port = {main_tub_endpoint}\n" |
|---|
| 84 | "tub.location = {main_tub_location}\n" |
|---|
| 85 | "web.port = {web_port_endpoint}\n".format( |
|---|
| 86 | main_tub_endpoint=main_tub_endpoint, |
|---|
| 87 | main_tub_location=main_tub_location, |
|---|
| 88 | web_port_endpoint=web_port_endpoint, |
|---|
| 89 | ) |
|---|
| 90 | ) |
|---|
| 91 | |
|---|
| 92 | intro_node = yield create_introducer(basedir) |
|---|
| 93 | ws = intro_node.getServiceNamed("webish") |
|---|
| 94 | |
|---|
| 95 | yield fireEventually(None) |
|---|
| 96 | intro_node.startService() |
|---|
| 97 | defer.returnValue((intro_node, ws)) |
|---|
| 98 | |
|---|
| 99 | |
|---|
| 100 | class IntroducerWeb(AsyncTestCase): |
|---|
| 101 | """ |
|---|
| 102 | Tests for web-facing functionality of an introducer node. |
|---|
| 103 | """ |
|---|
| 104 | def setUp(self): |
|---|
| 105 | self.node = None |
|---|
| 106 | self.port_assigner = SameProcessStreamEndpointAssigner() |
|---|
| 107 | self.port_assigner.setUp() |
|---|
| 108 | self.addCleanup(self.port_assigner.tearDown) |
|---|
| 109 | # Anything using Foolscap leaves some timer trash in the reactor that |
|---|
| 110 | # we have to arrange to have cleaned up. |
|---|
| 111 | self.addCleanup(lambda: flushEventualQueue(None)) |
|---|
| 112 | return super(IntroducerWeb, self).setUp() |
|---|
| 113 | |
|---|
| 114 | @defer.inlineCallbacks |
|---|
| 115 | def test_welcome(self): |
|---|
| 116 | node, ws = yield create_introducer_webish( |
|---|
| 117 | reactor, |
|---|
| 118 | self.port_assigner, |
|---|
| 119 | self.mktemp(), |
|---|
| 120 | ) |
|---|
| 121 | self.addCleanup(node.stopService) |
|---|
| 122 | |
|---|
| 123 | url = "http://localhost:%d/" % (ws.getPortnum(),) |
|---|
| 124 | res = yield do_http("get", url) |
|---|
| 125 | soup = BeautifulSoup(res, 'html5lib') |
|---|
| 126 | assert_soup_has_text(self, soup, u'Welcome to the Tahoe-LAFS Introducer') |
|---|
| 127 | assert_soup_has_favicon(self, soup) |
|---|
| 128 | assert_soup_has_text(self, soup, u'Page rendered at') |
|---|
| 129 | assert_soup_has_text(self, soup, u'Tahoe-LAFS code imported from:') |
|---|
| 130 | |
|---|
| 131 | @defer.inlineCallbacks |
|---|
| 132 | def test_basic_information(self): |
|---|
| 133 | """ |
|---|
| 134 | The introducer web page includes the software version and several other |
|---|
| 135 | simple pieces of information. |
|---|
| 136 | """ |
|---|
| 137 | node, ws = yield create_introducer_webish( |
|---|
| 138 | reactor, |
|---|
| 139 | self.port_assigner, |
|---|
| 140 | self.mktemp(), |
|---|
| 141 | ) |
|---|
| 142 | self.addCleanup(node.stopService) |
|---|
| 143 | |
|---|
| 144 | url = "http://localhost:%d/" % (ws.getPortnum(),) |
|---|
| 145 | res = yield do_http("get", url) |
|---|
| 146 | soup = BeautifulSoup(res, 'html5lib') |
|---|
| 147 | assert_soup_has_text( |
|---|
| 148 | self, |
|---|
| 149 | soup, |
|---|
| 150 | allmydata.__full_version__, |
|---|
| 151 | ) |
|---|
| 152 | assert_soup_has_text(self, soup, u"no peers!") |
|---|
| 153 | assert_soup_has_text(self, soup, u"subscribers!") |
|---|
| 154 | assert_soup_has_tag_with_attributes( |
|---|
| 155 | self, |
|---|
| 156 | soup, |
|---|
| 157 | "link", |
|---|
| 158 | {"href": "/tahoe.css"}, |
|---|
| 159 | ) |
|---|
| 160 | |
|---|
| 161 | @defer.inlineCallbacks |
|---|
| 162 | def test_tahoe_css(self): |
|---|
| 163 | """ |
|---|
| 164 | The introducer serves the css. |
|---|
| 165 | """ |
|---|
| 166 | node, ws = yield create_introducer_webish( |
|---|
| 167 | reactor, |
|---|
| 168 | self.port_assigner, |
|---|
| 169 | self.mktemp(), |
|---|
| 170 | ) |
|---|
| 171 | self.addCleanup(node.stopService) |
|---|
| 172 | |
|---|
| 173 | url = "http://localhost:%d/tahoe.css" % (ws.getPortnum(),) |
|---|
| 174 | |
|---|
| 175 | # Just don't return an error. If it does, do_http will raise |
|---|
| 176 | # something. |
|---|
| 177 | yield do_http("get", url) |
|---|
| 178 | |
|---|
| 179 | @defer.inlineCallbacks |
|---|
| 180 | def test_json_front_page(self): |
|---|
| 181 | """ |
|---|
| 182 | The front page can be served as json. |
|---|
| 183 | """ |
|---|
| 184 | node, ws = yield create_introducer_webish( |
|---|
| 185 | reactor, |
|---|
| 186 | self.port_assigner, |
|---|
| 187 | self.mktemp(), |
|---|
| 188 | ) |
|---|
| 189 | self.addCleanup(node.stopService) |
|---|
| 190 | |
|---|
| 191 | url = "http://localhost:%d/?t=json" % (ws.getPortnum(),) |
|---|
| 192 | res = yield do_http("get", url) |
|---|
| 193 | data = json.loads(res) |
|---|
| 194 | self.assertEqual(data["subscription_summary"], {}) |
|---|
| 195 | self.assertEqual(data["announcement_summary"], {}) |
|---|
| 196 | |
|---|
| 197 | |
|---|
| 198 | class IntroducerRootTests(SyncTestCase): |
|---|
| 199 | """ |
|---|
| 200 | Tests for ``IntroducerRoot``. |
|---|
| 201 | """ |
|---|
| 202 | def test_json(self): |
|---|
| 203 | """ |
|---|
| 204 | The JSON response includes totals for the number of subscriptions and |
|---|
| 205 | announcements of each service type. |
|---|
| 206 | """ |
|---|
| 207 | config = node.config_from_string(self.mktemp(), "", "") |
|---|
| 208 | config.get_private_path = lambda ignored: self.mktemp() |
|---|
| 209 | main_tub = Tub() |
|---|
| 210 | main_tub.listenOn(b"tcp:0") |
|---|
| 211 | main_tub.setLocation(b"tcp:127.0.0.1:1") |
|---|
| 212 | introducer_node = _IntroducerNode(config, main_tub, None, None) |
|---|
| 213 | |
|---|
| 214 | introducer_service = introducer_node.getServiceNamed("introducer") |
|---|
| 215 | for n in range(2): |
|---|
| 216 | introducer_service.add_subscriber( |
|---|
| 217 | FakeCanary(), |
|---|
| 218 | "arbitrary", |
|---|
| 219 | {"info": "info"}, |
|---|
| 220 | ) |
|---|
| 221 | |
|---|
| 222 | # It would be nice to use the publish method but then we have to |
|---|
| 223 | # generate a correctly signed message which I don't feel like doing. |
|---|
| 224 | ann_t = ("msg", "sig", "key") |
|---|
| 225 | ann = {"service-name": "arbitrary"} |
|---|
| 226 | introducer_service._announcements[("arbitrary", "key")] = ( |
|---|
| 227 | ann_t, |
|---|
| 228 | FakeCanary(), |
|---|
| 229 | ann, |
|---|
| 230 | 0, |
|---|
| 231 | ) |
|---|
| 232 | |
|---|
| 233 | resource = IntroducerRoot(introducer_node) |
|---|
| 234 | response = render(resource, {b"t": [b"json"]}) |
|---|
| 235 | expected = { |
|---|
| 236 | u"subscription_summary": {"arbitrary": 2}, |
|---|
| 237 | u"announcement_summary": {"arbitrary": 1}, |
|---|
| 238 | } |
|---|
| 239 | self.assertThat( |
|---|
| 240 | response, |
|---|
| 241 | succeeded(AfterPreprocessing(json.loads, Equals(expected)))) |
|---|