source: trunk/src/allmydata/test/web/test_common.py

Last change on this file was 1cfe843d, checked in by Alexandre Detiste <alexandre.detiste@…>, at 2024-02-22T23:40:25Z

more python2 removal

  • Property mode set to 100644
File size: 6.9 KB
Line 
1"""
2Tests for ``allmydata.web.common``.
3
4Ported to Python 3.
5"""
6
7import gc
8
9from bs4 import (
10    BeautifulSoup,
11)
12from hyperlink import (
13    DecodedURL,
14)
15
16from testtools.matchers import (
17    Equals,
18    Contains,
19    MatchesPredicate,
20    AfterPreprocessing,
21)
22from testtools.twistedsupport import (
23    failed,
24    succeeded,
25    has_no_result,
26)
27
28from twisted.python.failure import (
29    Failure,
30)
31from twisted.internet.error import (
32    ConnectionDone,
33)
34from twisted.internet.defer import (
35    Deferred,
36    fail,
37)
38from twisted.web.server import (
39    NOT_DONE_YET,
40)
41from twisted.web.resource import (
42    Resource,
43)
44
45from ...web.common import (
46    render_exception,
47)
48
49from ..common import (
50    SyncTestCase,
51)
52from ..common_web import (
53    render,
54)
55from .common import (
56    assert_soup_has_tag_with_attributes,
57)
58
59class StaticResource(Resource, object):
60    """
61    ``StaticResource`` is a resource that returns whatever Python object it is
62    given from its render method.  This is useful for testing
63    ``render_exception``\\ 's handling of different render results.
64    """
65    def __init__(self, response):
66        Resource.__init__(self)
67        self._response = response
68        self._request = None
69
70    @render_exception
71    def render(self, request):
72        self._request = request
73        return self._response
74
75
76class RenderExceptionTests(SyncTestCase):
77    """
78    Tests for ``render_exception`` (including the private helper ``_finish``).
79    """
80    def test_exception(self):
81        """
82        If the decorated method raises an exception then the exception is rendered
83        into the response.
84        """
85        class R(Resource):
86            @render_exception
87            def render(self, request):
88                raise Exception("synthetic exception")
89
90        self.assertThat(
91            render(R(), {}),
92            succeeded(
93                Contains(b"synthetic exception"),
94            ),
95        )
96
97    def test_failure(self):
98        """
99        If the decorated method returns a ``Deferred`` that fires with a
100        ``Failure`` then the exception the ``Failure`` wraps is rendered into
101        the response.
102        """
103        resource = StaticResource(fail(Exception("synthetic exception")))
104        self.assertThat(
105            render(resource, {}),
106            succeeded(
107                Contains(b"synthetic exception"),
108            ),
109        )
110
111    def test_resource(self):
112        """
113        If the decorated method returns an ``IResource`` provider then that
114        resource is used to render the response.
115        """
116        resource = StaticResource(StaticResource(b"static result"))
117        self.assertThat(
118            render(resource, {}),
119            succeeded(
120                Equals(b"static result"),
121            ),
122        )
123
124    def test_unicode(self):
125        """
126        If the decorated method returns a ``unicode`` string then that string is
127        UTF-8 encoded and rendered into the response.
128        """
129        text = u"\N{SNOWMAN}"
130        resource = StaticResource(text)
131        self.assertThat(
132            render(resource, {}),
133            succeeded(
134                Equals(text.encode("utf-8")),
135            ),
136        )
137
138    def test_bytes(self):
139        """
140        If the decorated method returns a ``bytes`` string then that string is
141        rendered into the response.
142        """
143        data = b"hello world"
144        resource = StaticResource(data)
145        self.assertThat(
146            render(resource, {}),
147            succeeded(
148                Equals(data),
149            ),
150        )
151
152    def test_decodedurl(self):
153        """
154        If the decorated method returns a ``DecodedURL`` then a redirect to that
155        location is rendered into the response.
156        """
157        loc = u"http://example.invalid/foo?bar=baz"
158        resource = StaticResource(DecodedURL.from_text(loc))
159        self.assertThat(
160            render(resource, {}),
161            succeeded(
162                MatchesPredicate(
163                    lambda value: assert_soup_has_tag_with_attributes(
164                        self,
165                        BeautifulSoup(value, 'html5lib'),
166                        "meta",
167                        {"http-equiv": "refresh",
168                         "content": "0;URL={}".format(loc),
169                        },
170                    )
171                    # The assertion will raise if it has a problem, otherwise
172                    # return None.  Turn the None into something
173                    # MatchesPredicate recognizes as success.
174                    or True,
175                    "did not find meta refresh tag in %r",
176                ),
177            ),
178        )
179
180    def test_none(self):
181        """
182        If the decorated method returns ``None`` then the response is finished
183        with no additional content.
184        """
185        self.assertThat(
186            render(StaticResource(None), {}),
187            succeeded(
188                Equals(b""),
189            ),
190        )
191
192    def test_not_done_yet(self):
193        """
194        If the decorated method returns ``NOT_DONE_YET`` then the resource is
195        responsible for finishing the request itself.
196        """
197        the_request = []
198        class R(Resource):
199            @render_exception
200            def render(self, request):
201                the_request.append(request)
202                return NOT_DONE_YET
203
204        d = render(R(), {})
205
206        self.assertThat(
207            d,
208            has_no_result(),
209        )
210
211        the_request[0].write(b"some content")
212        the_request[0].finish()
213
214        self.assertThat(
215            d,
216            succeeded(
217                Equals(b"some content"),
218            ),
219        )
220
221    def test_unknown(self):
222        """
223        If the decorated method returns something which is not explicitly
224        supported, an internal server error is rendered into the response.
225        """
226        self.assertThat(
227            render(StaticResource(object()), {}),
228            succeeded(
229                Equals(b"Internal Server Error"),
230            ),
231        )
232
233    def test_disconnected(self):
234        """
235        If the transport is disconnected before the response is available, no
236        ``RuntimeError`` is logged for finishing a disconnected request.
237        """
238        result = Deferred()
239        resource = StaticResource(result)
240        d = render(resource, {})
241
242        resource._request.connectionLost(Failure(ConnectionDone()))
243        result.callback(b"Some result")
244
245        self.assertThat(
246            d,
247            failed(
248                AfterPreprocessing(
249                    lambda reason: reason.type,
250                    Equals(ConnectionDone),
251                ),
252            ),
253        )
254
255        # Since we're not a trial TestCase we don't have flushLoggedErrors.
256        # The next best thing is to make sure any dangling Deferreds have been
257        # garbage collected and then let the generic trial logic for failing
258        # tests with logged errors kick in.
259        gc.collect()
Note: See TracBrowser for help on using the repository browser.