def test_asgi_conductor_raised_error_skips_shutdown():
    class SomeException(Exception):
        pass

    class Foo:
        def __init__(self):
            self.called_startup = False
            self.called_shutdown = False

        async def process_startup(self, scope, event):
            self.called_startup = True

        async def process_shutdown(self, scope, event):
            self.called_shutdown = True

    foo = Foo()

    app = App()
    app.add_middleware(foo)

    async def t():
        with pytest.raises(SomeException):
            async with testing.ASGIConductor(app):
                raise SomeException()

    falcon.async_to_sync(t)
    assert foo.called_startup
    assert not foo.called_shutdown
Beispiel #2
0
def test_lifespan_scope_version(spec_version, supported):
    app = App()

    shutting_down = asyncio.Condition()
    req_event_emitter = testing.ASGILifespanEventEmitter(shutting_down)
    resp_event_collector = testing.ASGIResponseEventCollector()

    scope = {
        'type': 'lifespan',
        'asgi': {'spec_version': spec_version, 'version': '3.0'}
    }

    if not supported:
        with pytest.raises(UnsupportedScopeError):
            falcon.async_to_sync(
                app.__call__, scope, req_event_emitter, resp_event_collector
            )

        return

    async def t():
        t = asyncio.get_event_loop().create_task(
            app(scope, req_event_emitter, resp_event_collector)
        )

        # NOTE(kgriffs): Yield to the lifespan task above
        await asyncio.sleep(0.001)

        async with shutting_down:
            shutting_down.notify()

        await t

    falcon.async_to_sync(t)
Beispiel #3
0
def test_lifespan_scope_default_version():
    app = App()

    resource = testing.SimpleTestResourceAsync()

    app.add_route('/', resource)

    shutting_down = asyncio.Condition()
    req_event_emitter = testing.ASGILifespanEventEmitter(shutting_down)
    resp_event_collector = testing.ASGIResponseEventCollector()

    scope = {'type': 'lifespan'}

    async def t():
        t = asyncio.get_event_loop().create_task(
            app(scope, req_event_emitter, resp_event_collector)
        )

        # NOTE(kgriffs): Yield to the lifespan task above
        await asyncio.sleep(0.001)

        async with shutting_down:
            shutting_down.notify()

        await t

    falcon.async_to_sync(t)

    assert not resource.called
def test_filelike():
    s = asgi.BoundedStream(testing.ASGIRequestEventEmitter())

    for __ in range(2):
        with pytest.raises(OSError):
            s.fileno()

        assert not s.isatty()
        assert s.readable()
        assert not s.seekable()
        assert not s.writable()

        s.close()

    assert s.closed

    # NOTE(kgriffs): Closing an already-closed stream is a noop.
    s.close()
    assert s.closed

    async def test_iteration():
        with pytest.raises(ValueError):
            await s.read()

        with pytest.raises(ValueError):
            await s.readall()

        with pytest.raises(ValueError):
            await s.exhaust()

        with pytest.raises(ValueError):
            async for chunk in s:
                pass

    falcon.async_to_sync(test_iteration)
Beispiel #5
0
def test_parser_async(body, doc):
    with disable_asgi_non_coroutine_wrapping():

        class WrappedRespondersBodyParserAsyncResource:
            @falcon.before(validate_param_async, 'limit', 100, is_async=True)
            @falcon.before(parse_body_async)
            async def on_get(self, req, resp, doc=None):
                self.doc = doc

            @falcon.before(parse_body_async, is_async=False)
            async def on_put(self, req, resp, doc=None):
                self.doc = doc

    app = create_app(asgi=True)

    resource = WrappedRespondersBodyParserAsyncResource()
    app.add_route('/', resource)

    testing.simulate_get(app, '/', body=body)
    assert resource.doc == doc

    testing.simulate_put(app, '/', body=body)
    assert resource.doc == doc

    async def test_direct():
        resource = WrappedRespondersBodyParserAsyncResource()

        req = testing.create_asgi_req()
        resp = create_resp(True)

        await resource.on_get(req, resp, doc)
        assert resource.doc == doc

    falcon.async_to_sync(test_direct)
def test_read_chunks(body, chunk_size):
    def stream():
        return _stream(body)

    async def test_nonmixed():
        s = stream()

        assert await s.read(0) == b''

        chunks = []

        while not s.eof:
            chunks.append(await s.read(chunk_size))

        assert b''.join(chunks) == body

    async def test_mixed_a():
        s = stream()

        chunks = []

        chunks.append(await s.read(chunk_size))
        chunks.append(await s.read(chunk_size))
        chunks.append(await s.readall())
        chunks.append(await s.read(chunk_size))

        assert b''.join(chunks) == body

    async def test_mixed_b():
        s = stream()

        chunks = []

        chunks.append(await s.read(chunk_size))
        chunks.append(await s.read(-1))

        assert b''.join(chunks) == body

    async def test_mixed_iter():
        s = stream()

        chunks = [await s.read(chunk_size)]
        chunks += [data async for data in s]

        assert b''.join(chunks) == body

    for t in (test_nonmixed, test_mixed_a, test_mixed_b, test_mixed_iter):
        falcon.async_to_sync(t)
        falcon.async_to_sync(t)
Beispiel #7
0
def _call_with_scope(scope):
    app = App()

    resource = testing.SimpleTestResourceAsync()

    app.add_route('/', resource)

    req_event_emitter = testing.ASGIRequestEventEmitter()
    resp_event_collector = testing.ASGIResponseEventCollector()

    falcon.async_to_sync(app.__call__, scope, req_event_emitter,
                         resp_event_collector)

    assert resource.called
    return resource
Beispiel #8
0
def test_bad_path(asgi, uri, patch_open):
    patch_open(b'')

    sr_type = StaticRouteAsync if asgi else StaticRoute
    sr = sr_type('/static', '/var/www/statics')

    req = _util.create_req(asgi, host='test.com', path=uri, root_path='statics')

    resp = _util.create_resp(asgi)

    with pytest.raises(falcon.HTTPNotFound):
        if asgi:
            falcon.async_to_sync(sr, req, resp)
        else:
            sr(req, resp)
Beispiel #9
0
def test_pathlib_path(asgi, patch_open):
    patch_open()

    sr = create_sr(asgi, '/static/', pathlib.Path('/var/www/statics'))
    req_path = '/static/css/test.css'

    req = _util.create_req(asgi,
                           host='test.com',
                           path=req_path,
                           root_path='statics')

    resp = _util.create_resp(asgi)

    if asgi:

        async def run():
            await sr(req, resp)
            return await resp.stream.read()

        body = falcon.async_to_sync(run)
    else:
        sr(req, resp)
        body = resp.stream.read()

    assert body.decode() == os.path.normpath('/var/www/statics/css/test.css')
Beispiel #10
0
def test_urlencoded_form_handler_serialize(data, expected):
    handler = media.URLEncodedFormHandler()
    assert handler.serialize(data, falcon.MEDIA_URLENCODED) == expected

    value = falcon.async_to_sync(handler.serialize_async, data,
                                 falcon.MEDIA_URLENCODED)
    assert value == expected
Beispiel #11
0
def test_good_path(asgi, uri_prefix, uri_path, expected_path, mtype,
                   monkeypatch):
    monkeypatch.setattr(io, 'open',
                        lambda path, mode: io.BytesIO(path.encode()))

    sr = create_sr(asgi, uri_prefix, '/var/www/statics')

    req_path = uri_prefix[:-1] if uri_prefix.endswith('/') else uri_prefix
    req_path += uri_path

    req = _util.create_req(asgi,
                           host='test.com',
                           path=req_path,
                           root_path='statics')

    resp = _util.create_resp(asgi)

    if asgi:

        async def run():
            await sr(req, resp)
            return await resp.stream.read()

        body = falcon.async_to_sync(run)
    else:
        sr(req, resp)
        body = resp.stream.read()

    assert resp.content_type in _MIME_ALTERNATIVE.get(mtype, (mtype, ))
    assert body.decode() == os.path.normpath('/var/www/statics' +
                                             expected_path)
Beispiel #12
0
def test_good_path(asgi, uri_prefix, uri_path, expected_path, mtype,
                   patch_open):
    patch_open()

    sr = create_sr(asgi, uri_prefix, '/var/www/statics')

    req_path = uri_prefix[:-1] if uri_prefix.endswith('/') else uri_prefix
    req_path += uri_path

    req = _util.create_req(asgi,
                           host='test.com',
                           path=req_path,
                           root_path='statics')

    resp = _util.create_resp(asgi)

    if asgi:

        async def run():
            await sr(req, resp)
            return await resp.stream.read()

        body = falcon.async_to_sync(run)
    else:
        sr(req, resp)
        body = resp.stream.read()

    assert resp.content_type in _MIME_ALTERNATIVE.get(mtype, (mtype, ))
    assert body.decode() == os.path.normpath('/var/www/statics' +
                                             expected_path)
    assert resp.headers.get('accept-ranges') == 'bytes'
Beispiel #13
0
def call_method(asgi, method_name, *args):
    resource = ResourceAsync() if asgi else Resource()

    if asgi:
        return falcon.async_to_sync(getattr(resource, method_name), *args)

    return getattr(resource, method_name)(*args)
Beispiel #14
0
def test_bad_path(asgi, uri, monkeypatch):
    monkeypatch.setattr(io, 'open', lambda path, mode: io.BytesIO())

    sr_type = StaticRouteAsync if asgi else StaticRoute
    sr = sr_type('/static', '/var/www/statics')

    req = _util.create_req(asgi,
                           host='test.com',
                           path=uri,
                           root_path='statics')

    resp = _util.create_resp(asgi)

    with pytest.raises(falcon.HTTPNotFound):
        if asgi:
            falcon.async_to_sync(sr, req, resp)
        else:
            sr(req, resp)
Beispiel #15
0
def test_exhaust_with_disconnect():
    async def t():
        emitter = testing.ASGIRequestEventEmitter(
            b'123456789' * 2,
            # NOTE(kgriffs): This must be small enough to create several events
            chunk_size=3,
        )
        s = asgi.BoundedStream(emitter)

        assert await s.read(1) == b'1'
        assert await s.read(2) == b'23'
        emitter.disconnect(exhaust_body=False)
        await s.exhaust()
        assert await s.read(1) == b''
        assert await s.read(100) == b''
        assert s.eof

    falcon.async_to_sync(t)
Beispiel #16
0
def async_take(source, count=None):
    async def collect():
        result = []
        async for item in source:
            result.append(item)
            if count is not None and count <= len(result):
                return result
        return result

    return falcon.async_to_sync(collect)
Beispiel #17
0
def test_supported_asgi_version(version, supported):
    scope = {
        'type': 'lifespan',
        'asgi': {
            'spec_version': '2.0',
            'version': version
        },
    }
    if version is None:
        del scope['asgi']['version']

    app = App()

    resource = testing.SimpleTestResourceAsync()
    app.add_route('/', resource)

    shutting_down = asyncio.Condition()
    req_event_emitter = testing.ASGILifespanEventEmitter(shutting_down)
    resp_event_collector = testing.ASGIResponseEventCollector()

    async def task():
        coro = asyncio.get_event_loop().create_task(
            app(scope, req_event_emitter, resp_event_collector))

        # NOTE(vytas): Yield to the lifespan task above.
        await asyncio.sleep(0)

        assert len(resp_event_collector.events) == 1
        event = resp_event_collector.events[0]
        if supported:
            assert event['type'] == 'lifespan.startup.complete'
        else:
            assert event['type'] == 'lifespan.startup.failed'
            assert event['message'].startswith(
                'Falcon requires ASGI version 3.x')

        async with shutting_down:
            shutting_down.notify()

        await coro

    falcon.async_to_sync(task)
Beispiel #18
0
def test_multiple_events_early_disconnect():
    class SomeResource:
        async def on_get(self, req, resp):
            async def emitter():
                while True:
                    yield SSEvent(data=b'whassup')
                    await asyncio.sleep(0.01)

            resp.sse = emitter()

    resource = SomeResource()

    app = App()
    app.add_route('/', resource)

    async def _test():
        conductor = testing.ASGIConductor(app)
        result = await conductor.simulate_get()
        assert 'data: whassup' in result.text

        async with testing.ASGIConductor(app) as conductor:
            # NOTE(vytas): Using the get_stream() alias.
            async with conductor.get_stream() as sr:

                event_count = 0

                result_text = ''

                while event_count < 5:
                    chunk = (await sr.stream.read()).decode()
                    if not chunk:
                        continue

                    result_text += chunk
                    event_count += len(chunk.strip().split('\n\n'))

                assert result_text.startswith('data: whassup\n\n' * 5)
                assert event_count == 5

    falcon.async_to_sync(_test)
Beispiel #19
0
def test_resource_with_uri_fields_async():
    app = create_app(asgi=True)

    resource = ClassResourceWithURIFieldsAsync()
    app.add_route('/{field1}/{field2}', resource)

    result = testing.simulate_get(app, '/a/b')

    assert result.status_code == 200
    assert result.headers['X-Fluffiness'] == 'fluffy'
    assert resource.fields == ('a', 'b')

    async def test_direct():
        resource = ClassResourceWithURIFieldsAsync()

        req = testing.create_asgi_req()
        resp = create_resp(True)

        await resource.on_get(req, resp, '1', '2')
        assert resource.fields == ('1', '2')

    falcon.async_to_sync(test_direct)
def test_iteration_already_started():
    body = testing.rand_string(1, 2048).encode()
    s = _stream(body)

    async def t():
        stream_iter = s.__aiter__()

        chunks = [await stream_iter.__anext__()]

        with pytest.raises(ValueError):
            stream_iter2 = s.__aiter__()
            await stream_iter2.__anext__()

        while True:
            try:
                chunks.append(await stream_iter.__anext__())
            except StopAsyncIteration:
                break

        assert b''.join(chunks) == body

    falcon.async_to_sync(t)
def test_serialization(asgi, func, body, expected):
    handler = media.JSONHandler(dumps=func)

    args = (body, b'application/javacript')

    if asgi:
        result = falcon.async_to_sync(handler.serialize_async, *args)
    else:
        result = handler.serialize(*args)

    # NOTE(nZac) PyPy and CPython render the final string differently. One
    # includes spaces and the other doesn't. This replace will normalize that.
    assert result.replace(b' ', b'') == expected  # noqa
Beispiel #22
0
def test_fallback_filename(asgi, uri, default, expected, content_type,
                           downloadable, patch_open, monkeypatch):
    def validate(path):
        if os.path.normpath(default) not in path:
            raise IOError()

    patch_open(validate=validate)

    monkeypatch.setattr('os.path.isfile',
                        lambda file: os.path.normpath(default) in file)

    sr = create_sr(
        asgi,
        '/static',
        '/var/www/statics',
        downloadable=downloadable,
        fallback_filename=default,
    )

    req_path = '/static/' + uri

    req = _util.create_req(asgi,
                           host='test.com',
                           path=req_path,
                           root_path='statics')
    resp = _util.create_resp(asgi)

    if asgi:

        async def run():
            await sr(req, resp)
            return await resp.stream.read()

        body = falcon.async_to_sync(run)
    else:
        sr(req, resp)
        body = resp.stream.read()

    assert sr.match(req.path)
    expected_content = os.path.normpath(
        os.path.join('/var/www/statics', expected))
    assert body.decode() == expected_content
    assert resp.content_type in _MIME_ALTERNATIVE.get(content_type,
                                                      (content_type, ))
    assert resp.headers.get('accept-ranges') == 'bytes'

    if downloadable:
        assert os.path.basename(expected) in resp.downloadable_as
    else:
        assert resp.downloadable_as is None
Beispiel #23
0
def test_fallback_filename(asgi, uri, default, expected, content_type,
                           downloadable, monkeypatch):
    def mock_open(path, mode):
        if os.path.normpath(default) in path:
            return io.BytesIO(path.encode())

        raise IOError()

    monkeypatch.setattr(io, 'open', mock_open)
    monkeypatch.setattr('os.path.isfile',
                        lambda file: os.path.normpath(default) in file)

    sr = create_sr(asgi,
                   '/static',
                   '/var/www/statics',
                   downloadable=downloadable,
                   fallback_filename=default)

    req_path = '/static/' + uri

    req = _util.create_req(asgi,
                           host='test.com',
                           path=req_path,
                           root_path='statics')
    resp = _util.create_resp(asgi)

    if asgi:

        async def run():
            await sr(req, resp)
            return await resp.stream.read()

        body = falcon.async_to_sync(run)
    else:
        sr(req, resp)
        body = resp.stream.read()

    assert sr.match(req.path)
    assert body.decode() == os.path.normpath(
        os.path.join('/var/www/statics', expected))
    assert resp.content_type in _MIME_ALTERNATIVE.get(content_type,
                                                      (content_type, ))

    if downloadable:
        assert os.path.basename(expected) in resp.downloadable_as
    else:
        assert resp.downloadable_as is None
def test_deserialization(asgi, func, body, expected):
    handler = media.JSONHandler(loads=func)

    args = ['application/javacript', len(body)]

    if asgi:
        if not ASGI_SUPPORTED:
            pytest.skip('ASGI requires Python 3.6+')

        from falcon.asgi.stream import BoundedStream

        s = BoundedStream(testing.ASGIRequestEventEmitter(body))
        args.insert(0, s)

        result = falcon.async_to_sync(handler.deserialize_async, *args)
    else:
        args.insert(0, io.BytesIO(body))
        result = handler.deserialize(*args)

    assert result == expected
def test_read_all(body, extra_body, set_content_length):
    if extra_body and not set_content_length:
        pytest.skip(
            'extra_body ignores set_content_length so we only need to test '
            'one of the parameter permutations'
        )

    expected_body = body if isinstance(body, bytes) else body.encode()

    def stream():
        stream_body = body
        content_length = None

        if extra_body:
            # NOTE(kgriffs): Test emitting more data than expected to the app
            content_length = len(expected_body)
            stream_body += b'\x00' if isinstance(stream_body, bytes) else '~'
        elif set_content_length:
            content_length = len(expected_body)

        return _stream(stream_body, content_length=content_length)

    async def test_iteration():
        s = stream()

        chunks = [chunk async for chunk in s]
        if not (expected_body or extra_body):
            assert not chunks

        assert b''.join(chunks) == expected_body
        assert await s.read() == b''
        assert await s.readall() == b''
        assert not [chunk async for chunk in s]
        assert s.tell() == len(expected_body)
        assert s.eof

    async def test_readall_a():
        s = stream()
        assert await s.readall() == expected_body
        assert await s.read() == b''
        assert await s.readall() == b''
        assert not [chunk async for chunk in s]
        assert s.tell() == len(expected_body)
        assert s.eof

    async def test_readall_b():
        s = stream()
        assert await s.read() == expected_body
        assert await s.readall() == b''
        assert await s.read() == b''
        assert not [chunk async for chunk in s]
        assert s.tell() == len(expected_body)
        assert s.eof

    async def test_readall_c():
        s = stream()
        body = await s.read(1)
        body += await s.read(None)
        assert body == expected_body
        assert s.tell() == len(expected_body)
        assert s.eof

    async def test_readall_d():
        s = stream()
        assert not s.closed

        if expected_body:
            assert not s.eof
        elif set_content_length:
            assert s.eof
        else:
            # NOTE(kgriffs): Stream doesn't know if there is more data
            #   coming or not until the first read.
            assert not s.eof

        assert s.tell() == 0

        assert await s.read(-2) == b''
        assert await s.read(-3) == b''
        assert await s.read(-100) == b''

        assert await s.read(-1) == expected_body
        assert await s.read(-1) == b''
        assert await s.readall() == b''
        assert await s.read() == b''
        assert not [chunk async for chunk in s]

        assert await s.read(-2) == b''

        assert s.tell() == len(expected_body)
        assert s.eof

        assert not s.closed
        s.close()
        assert s.closed

    for t in (test_iteration, test_readall_a, test_readall_b, test_readall_c, test_readall_d):
        falcon.async_to_sync(t)
Beispiel #26
0
def test_multiple_events():
    expected_result_text = ('data: ketchup\n'
                            '\n'
                            'event: condiment\n'
                            'data: mustard\n'
                            '\n'
                            'event: condiment\n'
                            'id: 1234\n'
                            'data: mayo\n'
                            '\n'
                            'event: topping\n'
                            'id: 5678\n'
                            'retry: 100\n'
                            'data: onions\n'
                            '\n'
                            ': Serve with chips.\n'
                            'retry: 100\n'
                            'data: guacamole \u1F951\n'
                            '\n'
                            'retry: 100\n'
                            'data: {"condiment": "salsa"}\n'
                            '\n')

    class SomeResource:
        async def on_get(self, req, resp):
            async def emitter():
                for event in [
                        SSEvent(data=b'ketchup'),
                        SSEvent(data=b'mustard', event='condiment'),
                        SSEvent(data=b'mayo',
                                event='condiment',
                                event_id='1234'),
                        SSEvent(data=b'onions',
                                event='topping',
                                event_id='5678',
                                retry=100),
                        SSEvent(text='guacamole \u1F951',
                                retry=100,
                                comment='Serve with chips.'),
                        SSEvent(json={'condiment': 'salsa'}, retry=100),
                ]:
                    yield event
                    await asyncio.sleep(0.001)

            resp.sse = emitter()

    resource = SomeResource()

    app = App()
    app.add_route('/', resource)

    client = testing.TestClient(app)

    async def _test():
        async with client as conductor:
            # NOTE(kgriffs): Single-shot test will only allow the first
            #   one or two events since a client disconnect will be emitted
            #   into the app immediately.
            result = await conductor.simulate_get()
            assert expected_result_text.startswith(result.text)

            async with conductor.simulate_get_stream() as sr:
                event_count = 0

                result_text = ''

                while True:
                    chunk = (await sr.stream.read()).decode()

                    if not chunk:
                        continue

                    result_text += chunk
                    event_count += len(chunk.strip().split('\n\n'))

                    if 'salsa' in chunk:
                        break

                assert not (await sr.stream.read())

                assert event_count == 6
                assert result_text == expected_result_text

    falcon.async_to_sync(_test)