Example #1
0
def test_parser_async(body, doc):
    with disable_asgi_non_coroutine_wrapping():

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

    app = create_app(asgi=True)

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

    testing.simulate_get(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

    testing.invoke_coroutine_sync(test_direct)
Example #2
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

    testing.invoke_coroutine_sync(t)

    assert not resource.called
Example #3
0
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

    testing.invoke_coroutine_sync(test_iteration)
Example #4
0
def test_asgi_request_event_emitter_hang():
    # NOTE(kgriffs): This tests the ASGI server behavior that
    #   ASGIRequestEventEmitter simulates when emit() is called
    #   again after there are not more events available.

    expected_elasped_min = 1
    disconnect_at = time.time() + expected_elasped_min

    emit = testing.ASGIRequestEventEmitter(disconnect_at=disconnect_at)

    async def t():
        start = time.time()
        while True:
            event = await emit()
            if not event.get('more_body', False):
                break
        elapsed = time.time() - start

        assert elapsed < 0.1

        start = time.time()
        await emit()
        elapsed = time.time() - start

        assert (elapsed + 0.1) > expected_elasped_min

    testing.invoke_coroutine_sync(t)
Example #5
0
def test_ignore_extra_asgi_events():
    collect = testing.ASGIResponseEventCollector()

    async def t():
        await collect({'type': 'http.response.start', 'status': 200})
        await collect({'type': 'http.response.body', 'more_body': False})

        # NOTE(kgriffs): Events after more_body is False are ignored to conform
        #   to the ASGI spec.
        await collect({'type': 'http.response.body'})
        assert len(collect.events) == 2

    testing.invoke_coroutine_sync(t)
Example #6
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()

    testing.invoke_coroutine_sync(app.__call__, scope, req_event_emitter,
                                  resp_event_collector)

    assert resource.called
    return resource
Example #7
0
def test_urlencoded_form_handler_serialize(data, expected):
    handler = media.URLEncodedFormHandler()
    assert handler.serialize(data, falcon.MEDIA_URLENCODED) == expected

    value = testing.invoke_coroutine_sync(handler.serialize_async, data,
                                          falcon.MEDIA_URLENCODED)
    assert value == expected
Example #8
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 = testing.invoke_coroutine_sync(run)
    else:
        sr(req, resp)
        body = resp.stream.read()

    assert resp.content_type == mtype
    assert body.decode() == '/var/www/statics' + expected_path
Example #9
0
def test_exhaust_with_disconnect():
    async def t():
        emitter = testing.ASGIRequestEventEmitter(b'123456798' * 1024,
                                                  disconnect_at=(time.time() +
                                                                 0.5))
        s = asgi.BoundedStream(emitter)

        assert await s.read(1) == b'1'
        assert await s.read(2) == b'23'
        await asyncio.sleep(0.5)
        await s.exhaust()
        assert await s.read(1) == b''
        assert await s.read(100) == b''
        assert s.eof

    testing.invoke_coroutine_sync(t)
Example #10
0
def test_invalid_asgi_events():
    collect = testing.ASGIResponseEventCollector()

    def make_event(headers=None, status=200):
        return {
            'type': 'http.response.start',
            'headers': headers or [],
            'status': status
        }

    async def t():
        with pytest.raises(TypeError):
            await collect({'type': 123})

        with pytest.raises(TypeError):
            headers = [('notbytes', b'bytes')]
            await collect(make_event(headers))

        with pytest.raises(TypeError):
            headers = [(b'bytes', 'notbytes')]
            await collect(make_event(headers))

        with pytest.raises(ValueError):
            headers = [
                # NOTE(kgriffs): Name must be lowercase
                (b'Content-Type', b'application/json')
            ]
            await collect(make_event(headers))

        with pytest.raises(TypeError):
            await collect(make_event(status='200'))

        with pytest.raises(TypeError):
            await collect(make_event(status=200.1))

        with pytest.raises(TypeError):
            await collect({'type': 'http.response.body', 'body': 'notbytes'})

        with pytest.raises(TypeError):
            await collect({'type': 'http.response.body', 'more_body': ''})

        with pytest.raises(ValueError):
            # NOTE(kgriffs): Invalid type
            await collect({'type': 'http.response.bod'})

    testing.invoke_coroutine_sync(t)
Example #11
0
def call_method(asgi, method_name, *args):
    resource = ResourceAsync() if asgi else Resource()

    if asgi:
        return testing.invoke_coroutine_sync(getattr(resource, method_name),
                                             *args)

    return getattr(resource, method_name)(*args)
Example #12
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:
            testing.invoke_coroutine_sync(sr, req, resp)
        else:
            sr(req, resp)
Example #13
0
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

    for t in (test_nonmixed, test_mixed_a, test_mixed_b):
        testing.invoke_coroutine_sync(t)
        testing.invoke_coroutine_sync(t)
Example #14
0
def test_serialization(asgi, func, body, expected):
    handler = media.JSONHandler(dumps=func)

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

    if asgi:
        result = testing.invoke_coroutine_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
Example #15
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')

    testing.invoke_coroutine_sync(test_direct)
Example #16
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):
            testing.invoke_coroutine_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

    testing.invoke_coroutine_sync(t)
Example #17
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 = testing.invoke_coroutine_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
Example #18
0
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 = testing.invoke_coroutine_sync(handler.deserialize_async,
                                               *args)
    else:
        args.insert(0, io.BytesIO(body))
        result = handler.deserialize(*args)

    assert result == expected
Example #19
0
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()
        assert b''.join([chunk async for chunk in s]) == expected_body
        assert await s.read() == b''
        assert await s.readall() == b''
        assert [chunk async for chunk in s][0] == b''
        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 [chunk async for chunk in s][0] == b''
        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 [chunk async for chunk in s][0] == b''
        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 [chunk async for chunk in s][0] == b''

        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):
        testing.invoke_coroutine_sync(t)